From 3004167181f9136a299ef05b83c4ea6d6066f6ee Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 5 Jun 2024 21:03:40 -0700 Subject: [PATCH 01/52] work in progress on getting context and almost passing it to smart rename --- .../UI/SmartRename/SmartRenameViewModel.cs | 32 ++++++++++++++++-- .../Lightup/ISmartRenameSessionWrapper.cs | 8 ++++- .../Core/InlineRename/InlineRenameService.cs | 7 ++++ .../Core/InlineRename/InlineRenameSession.cs | 33 +++++++++++-------- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 146fd082e8d13..798e357681db7 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -3,8 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -119,7 +122,7 @@ public SmartRenameViewModel( _smartRenameSession.PropertyChanged += SessionPropertyChanged; BaseViewModel = baseViewModel; - this.BaseViewModel.IdentifierText = baseViewModel.IdentifierText; + BaseViewModel.IdentifierText = baseViewModel.IdentifierText; GetSuggestionsCommand = new DelegateCommand(OnGetSuggestionsCommandExecute, null, threadingContext.JoinableTaskFactory); @@ -150,7 +153,30 @@ private void OnGetSuggestionsCommandExecute() _suggestionsDropdownTelemetry.DropdownButtonClickTimes += 1; } - _getSuggestionsTask = _smartRenameSession.GetSuggestionsAsync(_cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); + var allRenameLocations = BaseViewModel.Session.AllRenameLocationsTask.Join(); + ImmutableDictionary context = default; + if (allRenameLocations.Locations.Count > 0) + { + var references = new List(); + foreach (var renameLocation in allRenameLocations.Locations) + { + var syntaxTree = renameLocation.Document.GetSyntaxTreeSynchronously(_cancellationTokenSource.Token); + var text = syntaxTree.GetText(_cancellationTokenSource.Token); + var lineSpan = syntaxTree.GetLineSpan(renameLocation.TextSpan, _cancellationTokenSource.Token); + + var startLine = lineSpan.StartLinePosition.Line; + var endLine = lineSpan.EndLinePosition.Line; + + var documentContent = string.Join( + Environment.NewLine, + text.Lines.Skip(startLine).Take(endLine - startLine + 1).Select(l => l.ToString())); + references.Add(documentContent); + } + var contextBuilder = ImmutableDictionary.CreateBuilder(); + contextBuilder.Add("Reference", references.ToArray()); + context = contextBuilder.ToImmutableDictionary(); + } + _getSuggestionsTask = _smartRenameSession.GetSuggestionsAsync(context, _cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); } } @@ -196,7 +222,7 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) // The previous element of first element is the last one. And the next element of the last element is the first one. var currentIndex = SuggestedNames.IndexOf(currentIdentifier); currentIndex += down ? 1 : -1; - var count = this.SuggestedNames.Count; + var count = SuggestedNames.Count; currentIndex = (currentIndex + count) % count; return SuggestedNames[currentIndex]; } diff --git a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs index 78e2e0dda84c8..f4e0f4b93e04b 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -27,6 +28,7 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup; private static readonly Func> s_suggestedNamesAccessor; private static readonly Func>> s_getSuggestionsAsync; + private static readonly Func, CancellationToken, Task>> s_getSuggestionsAsync_WithContext; private static readonly Action s_onCancel; private static readonly Action s_onSuccess; @@ -44,13 +46,14 @@ static ISmartRenameSessionWrapper() s_suggestedNamesAccessor = LightupHelpers.CreatePropertyAccessor>(s_wrappedType, nameof(SuggestedNames), []); s_getSuggestionsAsync = LightupHelpers.CreateFunctionAccessor>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList()); + s_getSuggestionsAsync_WithContext = LightupHelpers.CreateFunctionAccessor, CancellationToken, Task>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(ImmutableDictionary), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList()); s_onCancel = LightupHelpers.CreateActionAccessor(s_wrappedType, nameof(OnCancel)); s_onSuccess = LightupHelpers.CreateActionAccessor(s_wrappedType, nameof(OnSuccess), typeof(string)); } private ISmartRenameSessionWrapper(object instance) { - this._instance = instance; + _instance = instance; } public bool IsAvailable => s_isAvailableAccessor(_instance); @@ -89,6 +92,9 @@ public static bool IsInstance([NotNullWhen(true)] object? instance) public Task> GetSuggestionsAsync(CancellationToken cancellationToken) => s_getSuggestionsAsync(_instance, cancellationToken); + public Task> GetSuggestionsAsync(ImmutableDictionary context, CancellationToken cancellationToken) + => s_getSuggestionsAsync(_instance, context, cancellationToken); + public void OnCancel() => s_onCancel(_instance); diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index 7697a502b5e02..9ef97212c57f6 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InlineRename; using Microsoft.CodeAnalysis.Navigation; @@ -85,6 +86,12 @@ public async Task StartInlineSessionAsync( return new InlineRenameSessionInfo(renameInfo.LocalizedErrorMessage); } + var symbolService = document.GetRequiredLanguageService(); + var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + document, textSpan.Start, cancellationToken).ConfigureAwait(false); + + var docComment = symbol.GetDocumentationCommentXml(); + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var snapshot = text.FindCorrespondingEditorTextSnapshot(); Contract.ThrowIfNull(snapshot, "The document used for starting the inline rename session should still be open and associated with a snapshot."); diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 4f30186d98c73..b300558ecd081 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -103,6 +103,11 @@ private set /// private JoinableTask _allRenameLocationsTask; + /// + /// Expose + /// + internal JoinableTask AllRenameLocationsTask => _allRenameLocationsTask; + /// /// The cancellation token for most work being done by the inline rename session. This /// includes the tasks. @@ -178,10 +183,10 @@ public InlineRenameSession( _previewChanges = previewChanges; _initialRenameText = triggerSpan.GetText(); - this.ReplacementText = _initialRenameText; + ReplacementText = _initialRenameText; _baseSolution = _triggerDocument.Project.Solution; - this.UndoManager = workspace.Services.GetService(); + UndoManager = workspace.Services.GetService(); FileRenameInfo = _renameInfo.GetFileRenameInfo(); @@ -246,7 +251,7 @@ private void InitializeOpenBuffers(SnapshotSpan triggerSpan) _triggerView.SetSelection(new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); } - this.UndoManager.CreateInitialState(this.ReplacementText, _triggerView.Selection, new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); + UndoManager.CreateInitialState(ReplacementText, _triggerView.Selection, new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); _openTextBuffers[triggerSpan.Snapshot.TextBuffer].SetReferenceSpans([startingSpan.ToTextSpan()]); UpdateReferenceLocationsTask(); @@ -441,7 +446,7 @@ internal void ApplyReplacementText(string replacementText, bool propagateEditImm { _threadingContext.ThrowIfNotOnUIThread(); VerifyNotDismissed(); - this.ReplacementText = _renameInfo.GetFinalSymbolName(replacementText); + ReplacementText = _renameInfo.GetFinalSymbolName(replacementText); var asyncToken = _asyncListener.BeginAsyncOperation(nameof(ApplyReplacementText)); @@ -512,12 +517,12 @@ private void UpdateConflictResolutionTask() // If the replacement text is empty, we do not update the results of the conflict // resolution task. We instead wait for a non-empty identifier. - if (this.ReplacementText == string.Empty) + if (ReplacementText == string.Empty) { return; } - var replacementText = this.ReplacementText; + var replacementText = ReplacementText; var options = _options; var cancellationToken = _conflictResolutionTaskCancellationSource.Token; @@ -545,7 +550,7 @@ private void QueueApplyReplacements() { // If the replacement text is empty, we do not update the results of the conflict // resolution task. We instead wait for a non-empty identifier. - if (this.ReplacementText == string.Empty) + if (ReplacementText == string.Empty) { return; } @@ -708,7 +713,7 @@ void DismissUIAndRollbackEdits() openBuffer.DisconnectAndRollbackEdits(isClosed); } - this.UndoManager.Disconnect(); + UndoManager.Disconnect(); if (_triggerView != null && !_triggerView.IsClosed) { @@ -753,8 +758,8 @@ private async Task CommitWorkerAsync(bool previewChanges, bool canUseBackg // still 'rename' even if the identifier went away (or was unchanged). But that isn't // a case we're aware of, so it's fine to be opinionated here that we can quickly bail // in these cases. - if (this.ReplacementText == string.Empty || - this.ReplacementText == _initialRenameText) + if (ReplacementText == string.Empty || + ReplacementText == _initialRenameText) { Cancel(); return false; @@ -764,7 +769,7 @@ private async Task CommitWorkerAsync(bool previewChanges, bool canUseBackg try { - if (canUseBackgroundWorkIndicator && this.RenameService.GlobalOptions.GetOption(InlineRenameSessionOptionsStorage.RenameAsynchronously)) + if (canUseBackgroundWorkIndicator && RenameService.GlobalOptions.GetOption(InlineRenameSessionOptionsStorage.RenameAsynchronously)) { // We do not cancel on edit because as part of the rename system we have asynchronous work still // occurring that itself may be asynchronously editing the buffer (for example, updating reference @@ -820,7 +825,7 @@ private async Task CommitCoreAsync(IUIThreadOperationContext operationContext, b newSolution = previewService.PreviewChanges( string.Format(EditorFeaturesResources.Preview_Changes_0, EditorFeaturesResources.Rename), "vs.csharp.refactoring.rename", - string.Format(EditorFeaturesResources.Rename_0_to_1_colon, this.OriginalSymbolName, this.ReplacementText), + string.Format(EditorFeaturesResources.Rename_0_to_1_colon, OriginalSymbolName, ReplacementText), _renameInfo.FullDisplayName, _renameInfo.Glyph, newSolution, @@ -870,7 +875,7 @@ await DismissUIAndRollbackEditsAndEndRenameSessionAsync( using var undoTransaction = _workspace.OpenGlobalUndoTransaction(EditorFeaturesResources.Inline_Rename); - if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, this.ReplacementText)) + if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, ReplacementText)) return (NotificationSeverity.Error, EditorFeaturesResources.Rename_operation_was_cancelled_or_is_not_valid); if (!_workspace.TryApplyChanges(finalSolution)) @@ -897,7 +902,7 @@ await DismissUIAndRollbackEditsAndEndRenameSessionAsync( .SelectMany(c => c.GetChangedDocuments().Concat(c.GetAddedDocuments())) .ToList(); - if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, this.ReplacementText)) + if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, ReplacementText)) return (NotificationSeverity.Information, EditorFeaturesResources.Rename_operation_was_not_properly_completed_Some_file_might_not_have_been_updated); return null; From 797896bedbc175380365237af92d671f76616f27 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 6 Jun 2024 10:24:15 -0700 Subject: [PATCH 02/52] pass context to SmartRenameSession --- .../Lightup/ISmartRenameSessionWrapper.cs | 2 +- .../Core.Wpf/Lightup/LightupHelpers.cs | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs index f4e0f4b93e04b..0be4f0b3a51e4 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs @@ -93,7 +93,7 @@ public Task> GetSuggestionsAsync(CancellationToken cancell => s_getSuggestionsAsync(_instance, cancellationToken); public Task> GetSuggestionsAsync(ImmutableDictionary context, CancellationToken cancellationToken) - => s_getSuggestionsAsync(_instance, context, cancellationToken); + => s_getSuggestionsAsync_WithContext(_instance, context, cancellationToken); public void OnCancel() => s_onCancel(_instance); diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index 6fda65becc9c9..281c27912fc17 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -352,6 +352,91 @@ public static Func CreateFunctionAccessor(Ty return expression.Compile(); } + /// + /// Generates a compiled accessor method for a property which cannot be bound at compile time. + /// + /// The compile-time type representing the instance on which the property is defined. This + /// may be a superclass of the actual type on which the property is declared if the declaring type is not + /// available at compile time. + /// The compile-time type representing the type of the first argument. This + /// may be a superclass of the actual type of the argument if the declared type is not available at compile + /// time. + /// The compile-type type representing the result of the property. This may be a + /// superclass of the actual type of the property if the property type is not available at compile + /// time. + /// The runtime time on which the property is defined. If this value is null, the runtime + /// time is assumed to not exist, and a fallback accessor returning will be + /// generated. + /// The name of the method to access. + /// The value to return if the method is not available at runtime. + /// An accessor method to access the specified runtime property. + public static Func CreateFunctionAccessor(Type? type, string methodName, Type? arg0Type, Type? arg1Type, TResult defaultValue) + { + if (methodName is null) + { + throw new ArgumentNullException(nameof(methodName)); + } + + if (type == null) + { + throw new NotImplementedException(); + //return CreateFallbackFunction(defaultValue); + } + + if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'"); + } + + var method = type.GetTypeInfo().GetDeclaredMethods(methodName).Single(method => + { + var parameters = method.GetParameters(); + return parameters is [{ ParameterType: var parameter0Type }, { ParameterType: var parameter1Type }] && parameter0Type == arg0Type && parameter1Type == arg1Type; + }); + + var parameters = method.GetParameters(); + if (arg0Type != parameters[0].ParameterType) + { + throw new ArgumentException($"Type '{arg0Type}' was expected to match parameter type '{parameters[0].ParameterType}'", nameof(arg0Type)); + } + if (arg1Type != parameters[1].ParameterType) + { + throw new ArgumentException($"Type '{arg1Type}' was expected to match parameter type '{parameters[1].ParameterType}'", nameof(arg1Type)); + } + + if (!typeof(TResult).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) + { + throw new InvalidOperationException($"Method '{method}' produces a value of type '{method.ReturnType}', which is not assignable to type '{typeof(TResult)}'"); + } + + var parameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T))); + var argument0 = Expression.Parameter(typeof(TArg0), parameters[0].Name); + var argument1 = Expression.Parameter(typeof(TArg1), parameters[1].Name); + var instance = + type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) + ? (Expression)parameter + : Expression.Convert(parameter, type); + var convertedArgument0 = + arg0Type.GetTypeInfo().IsAssignableFrom(typeof(TArg0).GetTypeInfo()) + ? (Expression)argument0 + : Expression.Convert(argument0, arg0Type); + var convertedArgument1 = + arg1Type.GetTypeInfo().IsAssignableFrom(typeof(TArg1).GetTypeInfo()) + ? (Expression)argument1 + : Expression.Convert(argument1, arg1Type); + + var expression = + Expression.Lambda>( + Expression.Convert( + Expression.Call( + instance, + method, + convertedArgument0, convertedArgument1), typeof(TResult)), + parameter, + argument0, argument1); + return expression.Compile(); + } + private static string GenerateParameterName(Type parameterType) { var typeName = parameterType.Name; From f306f0569c5b6fecf5657091098322278626d482 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 6 Jun 2024 10:38:55 -0700 Subject: [PATCH 03/52] attempt to set PromptOverride through Lightup --- .../UI/SmartRename/SmartRenameViewModel.cs | 9 +++ .../Lightup/ISmartRenameSessionWrapper.cs | 3 + .../Core.Wpf/Lightup/LightupHelpers.cs | 56 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 798e357681db7..9cda8eedc3765 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -176,6 +176,15 @@ private void OnGetSuggestionsCommandExecute() contextBuilder.Add("Reference", references.ToArray()); context = contextBuilder.ToImmutableDictionary(); } + _smartRenameSession.PromptOverride = """ + Your task is to help a software developer improve the identifier name indicated by [NameThisIdentifier]. The existing identifier name is {identifier} + + Use the following information as context: + + {context} + + Given the provided information, generate five suggestions to rename the selected symbol. The suggested name should match the style of similar identifiers in the provided [CODE]. Put the suggestions in a JSON array called SuggestedNames and return the json object only as a response. Do not include any markdown formatting. Here are an example of the RESPONSE format: { ""SuggestedNames"": [""..."", ""..."", ""..."", ""..."", ""...""] } + """; _getSuggestionsTask = _smartRenameSession.GetSuggestionsAsync(context, _cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); } } diff --git a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs index 0be4f0b3a51e4..be28711aa3428 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs @@ -25,6 +25,7 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup; private static readonly Func s_isInProgressAccessor; private static readonly Func s_statusMessageAccessor; private static readonly Func s_statusMessageVisibilityAccessor; + private static readonly Func s_promptOverrideSetter; private static readonly Func> s_suggestedNamesAccessor; private static readonly Func>> s_getSuggestionsAsync; @@ -43,6 +44,7 @@ static ISmartRenameSessionWrapper() s_isInProgressAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(IsInProgress), false); s_statusMessageAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(StatusMessage), ""); s_statusMessageVisibilityAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(StatusMessageVisibility), false); + s_promptOverrideSetter = LightupHelpers.CreatePropertySetter(s_wrappedType, nameof(PromptOverride), typeof(string), typeof(object)); s_suggestedNamesAccessor = LightupHelpers.CreatePropertyAccessor>(s_wrappedType, nameof(SuggestedNames), []); s_getSuggestionsAsync = LightupHelpers.CreateFunctionAccessor>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList()); @@ -61,6 +63,7 @@ private ISmartRenameSessionWrapper(object instance) public bool IsInProgress => s_isInProgressAccessor(_instance); public string StatusMessage => s_statusMessageAccessor(_instance); public bool StatusMessageVisibility => s_statusMessageVisibilityAccessor(_instance); + public string PromptOverride { set { s_promptOverrideSetter(_instance, value); } } public IReadOnlyList SuggestedNames => s_suggestedNamesAccessor(_instance); public event PropertyChangedEventHandler PropertyChanged diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index 281c27912fc17..10175daddd730 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -96,6 +96,62 @@ public static Func CreatePropertyAccessor(Type? type, st return expression.Compile(); } + /// + /// Generates a compiled setter method for a property which cannot be bound at compile time. + /// + /// The compile-time type representing the instance on which the property is defined. This + /// may be a superclass of the actual type on which the property is declared if the declaring type is not + /// available at compile time. + /// The compile-type type representing the type of the property. This may be a + /// superclass of the actual type of the property if the property type is not available at compile + /// time. + /// The runtime time on which the property is defined. + /// The name of the property to access. + /// The value to set. + /// An accessor method to access the specified runtime property. + public static Func CreatePropertySetter(Type? type, string propertyName, Type valueType, Type returnType) + { + if (propertyName is null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + if (type == null) + { + throw new NotImplementedException(); + //return CreateFallbackAccessor(defaultValue); + } + + if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'"); + } + + var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); + if (property == null) + { + throw new NotImplementedException(); + //return CreateFallbackAccessor(defaultValue); + } + + if (!typeof(TValue).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) + { + throw new InvalidOperationException($"Property '{property}' produces a value of type '{property.PropertyType}', which is not assignable to type '{typeof(TValue)}'"); + } + + var parameter = Expression.Parameter(typeof(TValue), GenerateParameterName(typeof(TValue))); + var instance = + valueType.GetTypeInfo().IsAssignableFrom(typeof(TValue).GetTypeInfo()) + ? (Expression)parameter + : Expression.Convert(parameter, type); + + var expression = + Expression.Lambda>( + Expression.Convert(Expression.Call(instance, property.SetMethod), typeof(TValue)), + parameter); + return expression.Compile(); + } + /// /// Generates a compiled accessor method for a method with a signature compatible with which /// cannot be bound at compile time. From dc2c255da45f6f5c8585cd246942c5b2453fca32 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 6 Jun 2024 14:28:12 -0700 Subject: [PATCH 04/52] reflection refinement --- .../Lightup/ISmartRenameSessionWrapper.cs | 4 ++-- .../Core.Wpf/Lightup/LightupHelpers.cs | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs index be28711aa3428..f450b711208b8 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs @@ -25,7 +25,7 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup; private static readonly Func s_isInProgressAccessor; private static readonly Func s_statusMessageAccessor; private static readonly Func s_statusMessageVisibilityAccessor; - private static readonly Func s_promptOverrideSetter; + private static readonly Action s_promptOverrideSetter; private static readonly Func> s_suggestedNamesAccessor; private static readonly Func>> s_getSuggestionsAsync; @@ -44,7 +44,7 @@ static ISmartRenameSessionWrapper() s_isInProgressAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(IsInProgress), false); s_statusMessageAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(StatusMessage), ""); s_statusMessageVisibilityAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(StatusMessageVisibility), false); - s_promptOverrideSetter = LightupHelpers.CreatePropertySetter(s_wrappedType, nameof(PromptOverride), typeof(string), typeof(object)); + s_promptOverrideSetter = LightupHelpers.CreatePropertySetter(s_wrappedType, nameof(PromptOverride), typeof(string)); s_suggestedNamesAccessor = LightupHelpers.CreatePropertyAccessor>(s_wrappedType, nameof(SuggestedNames), []); s_getSuggestionsAsync = LightupHelpers.CreateFunctionAccessor>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList()); diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index 10175daddd730..a0e52ccccf805 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.Build.Framework.XamlTypes; namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup; @@ -109,7 +110,7 @@ public static Func CreatePropertyAccessor(Type? type, st /// The name of the property to access. /// The value to set. /// An accessor method to access the specified runtime property. - public static Func CreatePropertySetter(Type? type, string propertyName, Type valueType, Type returnType) + public static Action CreatePropertySetter(Type? type, string propertyName, Type valueType) { if (propertyName is null) { @@ -134,21 +135,34 @@ public static Func CreatePropertySetter(Type? type //return CreateFallbackAccessor(defaultValue); } + var parameters = property.GetSetMethod().GetParameters(); + if (valueType != parameters[0].ParameterType) + { + throw new ArgumentException($"Type '{valueType}' was expected to match parameter type '{parameters[0].ParameterType}'", nameof(valueType)); + } + if (!typeof(TValue).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException($"Property '{property}' produces a value of type '{property.PropertyType}', which is not assignable to type '{typeof(TValue)}'"); } var parameter = Expression.Parameter(typeof(TValue), GenerateParameterName(typeof(TValue))); + var argument = Expression.Parameter(typeof(TValue), parameters[0].Name); var instance = - valueType.GetTypeInfo().IsAssignableFrom(typeof(TValue).GetTypeInfo()) + type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) ? (Expression)parameter : Expression.Convert(parameter, type); + var convertedArgument = + valueType.GetTypeInfo().IsAssignableFrom(typeof(TValue).GetTypeInfo()) + ? (Expression)argument + : Expression.Convert(argument, valueType); + var expression = - Expression.Lambda>( - Expression.Convert(Expression.Call(instance, property.SetMethod), typeof(TValue)), - parameter); + Expression.Lambda>( + Expression.Call(instance, property.SetMethod, convertedArgument), + parameter, + argument); return expression.Compile(); } From 3cba7f9336ce9a7fe71523d9df5cd4562eaf6897 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 6 Jun 2024 15:16:17 -0700 Subject: [PATCH 05/52] Fix CreatePropertySetter in LightupHelpers --- .../Core.Wpf/Lightup/LightupHelpers.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index a0e52ccccf805..06418ba4010b0 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -146,23 +146,23 @@ public static Action CreatePropertySetter(Type? type, stri throw new InvalidOperationException($"Property '{property}' produces a value of type '{property.PropertyType}', which is not assignable to type '{typeof(TValue)}'"); } - var parameter = Expression.Parameter(typeof(TValue), GenerateParameterName(typeof(TValue))); - var argument = Expression.Parameter(typeof(TValue), parameters[0].Name); + var instanceParameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T))); var instance = type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) - ? (Expression)parameter - : Expression.Convert(parameter, type); + ? (Expression)instanceParameter + : Expression.Convert(instanceParameter, type); + var parameter = Expression.Parameter(typeof(TValue), parameters[0].Name); var convertedArgument = valueType.GetTypeInfo().IsAssignableFrom(typeof(TValue).GetTypeInfo()) - ? (Expression)argument - : Expression.Convert(argument, valueType); + ? (Expression)parameter + : Expression.Convert(parameter, valueType); var expression = Expression.Lambda>( Expression.Call(instance, property.SetMethod, convertedArgument), - parameter, - argument); + instanceParameter, + parameter); return expression.Compile(); } From e03d4b8d2abef0e721d111573c2604ee45309040 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 13:35:38 -0700 Subject: [PATCH 06/52] Find definitions and references and add them to the context --- .../CSharpEditorInlineRenameService.cs | 89 +++ .../InlineRename/CSharpRenameContextHelper.cs | 636 ++++++++++++++++++ .../UI/SmartRename/SmartRenameViewModel.cs | 13 +- .../VSTypeScriptEditorInlineRenameService.cs | 7 + .../AbstractEditorInlineRenameService.cs | 13 + .../IEditorInlineRenameService.cs | 12 +- .../Core/InlineRename/InlineRenameService.cs | 6 + .../Core/InlineRename/InlineRenameSession.cs | 3 + .../Editor/FSharpEditorInlineRenameService.cs | 7 +- .../XamlEditorInlineRenameService.cs | 5 + 10 files changed, 783 insertions(+), 8 deletions(-) create mode 100644 src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index bbb8adf16055d..4fff7edfb7c0a 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -4,10 +4,16 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.CSharp.InlineRename; @@ -18,4 +24,87 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { + protected override async Task> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + { + var seen = PooledHashSet.GetInstance(); + var definitions = ArrayBuilder.GetInstance(); + var references = ArrayBuilder.GetInstance(); + + foreach (var renameDefinition in renameInfo.DefinitionLocations) + { + var containingStatementOrDeclarationSpan = + await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? + await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false); + + var syntaxTree = await renameDefinition.Document.GetSyntaxTreeAsync(cancellationToken); + var textSpan = await syntaxTree?.GetTextAsync(cancellationToken); + var lineSpan = syntaxTree?.GetLineSpan(renameDefinition.SourceSpan, cancellationToken); + + if (lineSpan is null || textSpan is null) + { + continue; + } + + AddSpanOfInterest(textSpan, lineSpan.Value, containingStatementOrDeclarationSpan, definitions); + } + + var renameLocationOptions = new Rename.SymbolRenameOptions(RenameOverloads: true, RenameInStrings: true, RenameInComments: true); + var renameLocations = await renameInfo.FindRenameLocationsAsync(renameLocationOptions, cancellationToken); + foreach (var renameLocation in renameLocations.Locations) + { + var containingStatementOrDeclarationSpan = + await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? + await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false); + + var syntaxTree = await renameLocation.Document.GetSyntaxTreeAsync(cancellationToken); + var textSpan = await syntaxTree?.GetTextAsync(cancellationToken); + var lineSpan = syntaxTree?.GetLineSpan(renameLocation.TextSpan, cancellationToken); + + if (lineSpan is null || textSpan is null) + { + continue; + } + + AddSpanOfInterest(textSpan, lineSpan.Value, containingStatementOrDeclarationSpan, references); + } + + var context = ImmutableDictionary.Empty + .Add("definition", definitions.ToArrayAndFree()) + .Add("reference", references.ToArrayAndFree()); + return context; + + void AddSpanOfInterest(SourceText documentText, FileLinePositionSpan lineSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder resultBuilder) + { + int startPosition, endPosition, startLine = 0, endLine = 0, lineCount = 0; + if (surroundingSpanOfInterest is not null) + { + startPosition = surroundingSpanOfInterest.Value.Start; + endPosition = surroundingSpanOfInterest.Value.End; + startLine = lineSpan.StartLinePosition.Line; + endLine = lineSpan.EndLinePosition.Line; + lineCount = endLine - startLine + 1; + } + + // If a well defined surrounding span was not computed or if the computed surrounding span was too large, + // select a span that encompasses 5 lines above and 5 lines below the error squiggle. + if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > 10) + { + startLine = Math.Max(0, lineSpan.StartLinePosition.Line - 5); + endLine = Math.Min(documentText.Lines.Count - 1, lineSpan.EndLinePosition.Line + 5); + } + + // If the start and end positions are not at the beginning and end of the start and end lines respectively, + // expand to select the corresponding lines completely. + startPosition = documentText.Lines[startLine].Start; + endPosition = documentText.Lines[endLine].End; + var length = endPosition - startPosition + 1; + + surroundingSpanOfInterest = new TextSpan(startPosition, length); + + if (seen.Add(surroundingSpanOfInterest.Value)) + { + resultBuilder.Add(documentText.GetSubText(surroundingSpanOfInterest.Value).ToString()); + } + } + } } diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs new file mode 100644 index 0000000000000..c7e5fac517a86 --- /dev/null +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs @@ -0,0 +1,636 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Copied from VisualStudio.Conversations repo src/Copilot.Vsix/QuickActions/CSharp/CSharpTypeSignatureHelper.cs +// with minor changes + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.InlineRename +{ + internal static class RenameContextHelper + { + private const string Space = " "; + private const string Indentation = " "; + private const string OpeningBrace = "{"; + private const string ClosingBrace = "}"; + private const string Semicolon = ";"; + private const string ColonSeparator = " : "; + private const string CommaSeparator = ", "; + private const string Unknown = "?"; + + private const SymbolDisplayGenericsOptions GenericsOptions = + SymbolDisplayGenericsOptions.IncludeVariance | + SymbolDisplayGenericsOptions.IncludeTypeParameters; + + private const SymbolDisplayMiscellaneousOptions MiscellaneousOptions = + SymbolDisplayMiscellaneousOptions.UseSpecialTypes | + SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral | + SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | + SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix | + SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName; + + private static readonly SymbolDisplayFormat FormatForTypeDefinitions = + new(typeQualificationStyle: + SymbolDisplayTypeQualificationStyle.NameOnly, + genericsOptions: + GenericsOptions | + SymbolDisplayGenericsOptions.IncludeTypeConstraints, + delegateStyle: + SymbolDisplayDelegateStyle.NameAndSignature, + kindOptions: + SymbolDisplayKindOptions.IncludeTypeKeyword, + miscellaneousOptions: + MiscellaneousOptions); + + private static readonly SymbolDisplayFormat FormatForBaseTypeAndInterfacesInTypeDefinition = + new(typeQualificationStyle: + SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: + GenericsOptions, + miscellaneousOptions: + MiscellaneousOptions); + + private static readonly SymbolDisplayFormat FormatForMemberDefinitions = + new(typeQualificationStyle: + SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: + GenericsOptions | + SymbolDisplayGenericsOptions.IncludeTypeConstraints, + memberOptions: + SymbolDisplayMemberOptions.IncludeAccessibility | + SymbolDisplayMemberOptions.IncludeModifiers | + SymbolDisplayMemberOptions.IncludeRef | + SymbolDisplayMemberOptions.IncludeType | + SymbolDisplayMemberOptions.IncludeExplicitInterface | + SymbolDisplayMemberOptions.IncludeParameters | + SymbolDisplayMemberOptions.IncludeConstantValue, + extensionMethodStyle: + SymbolDisplayExtensionMethodStyle.StaticMethod, + parameterOptions: + SymbolDisplayParameterOptions.IncludeModifiers | + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeType | + SymbolDisplayParameterOptions.IncludeName | + SymbolDisplayParameterOptions.IncludeOptionalBrackets | + SymbolDisplayParameterOptions.IncludeDefaultValue, + propertyStyle: + SymbolDisplayPropertyStyle.ShowReadWriteDescriptor, + kindOptions: + SymbolDisplayKindOptions.IncludeMemberKeyword, + miscellaneousOptions: + MiscellaneousOptions | + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + private static StringBuilder AppendKeyword(this StringBuilder builder, SyntaxKind keywordKind) + => builder.Append(SyntaxFacts.GetText(keywordKind)); + + // SymbolDisplay does not support displaying accessibility for types at the moment. + // See https://github.com/dotnet/roslyn/issues/28297. + private static void AppendAccessibility(this StringBuilder builder, ITypeSymbol type) + { + switch (type.DeclaredAccessibility) + { + case Accessibility.Private: + builder.AppendKeyword(SyntaxKind.PrivateKeyword); + break; + + case Accessibility.Internal: + builder.AppendKeyword(SyntaxKind.InternalKeyword); + break; + + case Accessibility.ProtectedAndInternal: + builder.AppendKeyword(SyntaxKind.PrivateKeyword); + builder.Append(Space); + builder.AppendKeyword(SyntaxKind.ProtectedKeyword); + break; + + case Accessibility.Protected: + builder.AppendKeyword(SyntaxKind.ProtectedKeyword); + break; + + case Accessibility.ProtectedOrInternal: + builder.AppendKeyword(SyntaxKind.ProtectedKeyword); + builder.Append(Space); + builder.AppendKeyword(SyntaxKind.InternalKeyword); + break; + + case Accessibility.Public: + builder.AppendKeyword(SyntaxKind.PublicKeyword); + break; + + default: + builder.Append(Unknown); + break; + } + + builder.Append(Space); + } + + // SymbolDisplay does not support displaying modifiers for types at the moment. + // See https://github.com/dotnet/roslyn/issues/28297. + private static void AppendModifiers(this StringBuilder builder, ITypeSymbol type) + { + if (type.TypeKind is TypeKind.Class && type.IsAbstract) + { + builder.AppendKeyword(SyntaxKind.AbstractKeyword); + builder.Append(Space); + } + + if (type.TypeKind is TypeKind.Class && type.IsSealed) + { + builder.AppendKeyword(SyntaxKind.SealedKeyword); + builder.Append(Space); + } + + if (type.IsStatic) + { + builder.AppendKeyword(SyntaxKind.StaticKeyword); + builder.Append(Space); + } + } + + private static void AppendBaseTypeAndInterfaces( + this StringBuilder builder, + ITypeSymbol type, + CancellationToken cancellationToken) + { + var baseType = type.BaseType; + + var baseTypeAndInterfaces = + baseType is null || + baseType.SpecialType is SpecialType.System_Object || + baseType.SpecialType is SpecialType.System_ValueType + ? type.AllInterfaces + : [baseType, .. type.AllInterfaces]; + + if (baseTypeAndInterfaces.IsDefaultOrEmpty) + { + return; + } + + builder.Append(ColonSeparator); + + for (var i = 0; i < baseTypeAndInterfaces.Length; ++i) + { + cancellationToken.ThrowIfCancellationRequested(); + + var baseTypeOrInterface = baseTypeAndInterfaces[i]; + + builder.Append( + Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(baseTypeOrInterface, FormatForBaseTypeAndInterfacesInTypeDefinition)); + + if (i < baseTypeAndInterfaces.Length - 1) + { + builder.Append(CommaSeparator); + } + } + } + + private static void AppendTypeDefinition( + this StringBuilder builder, + ITypeSymbol type, + CancellationToken cancellationToken) + { + builder.AppendAccessibility(type); + builder.AppendModifiers(type); + builder.Append(Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(type, FormatForTypeDefinitions)); + + if (type.TypeKind is TypeKind.Delegate) + { + builder.Append(Semicolon); + } + else + { + builder.AppendBaseTypeAndInterfaces(type, cancellationToken); + } + } + + private static void AppendMemberDefinition(this StringBuilder builder, ISymbol member) + { + Assumes.True( + member is IEventSymbol || + member is IFieldSymbol || + member is IPropertySymbol || + member is IMethodSymbol); + + var line = Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(member, FormatForMemberDefinitions); + builder.Append(line); + + if (!line.EndsWith(ClosingBrace)) + { + builder.Append(Semicolon); + } + } + + private static string GetIndentation(int level) + { + if (level is 0) + { + return string.Empty; + } + + if (level is 1) + { + return Indentation; + } + + var builder = PooledStringBuilder.GetInstance(); + + for (var i = 0; i < level; ++i) + { + builder.Builder.Append(Indentation); + } + + return builder.ToStringAndFree(); + } + + /// + /// Returns the type signature for the type specified by the supplied . + /// + /// + /// Signatures for all contained members are also included within a type's signature but method bodies are not. + /// + public static string GetSignature( + this ITypeSymbol type, + int indentLevel, + CancellationToken cancellationToken) + { + var builder = PooledStringBuilder.GetInstance(); + var indentation = GetIndentation(indentLevel); + var memberIndentation = GetIndentation(++indentLevel); + + builder.Builder.Append(indentation); + builder.Builder.AppendTypeDefinition(type, cancellationToken); + + if (type.TypeKind is not TypeKind.Delegate) + { + builder.Builder.AppendLine(); + builder.Builder.Append(indentation); + builder.Builder.AppendLine(OpeningBrace); + } + + foreach (var member in type.GetMembers()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (member.CanBeReferencedByName && + !member.IsImplicitlyDeclared && + !member.HasUnsupportedMetadata) + { + if (member is IEventSymbol || + member is IFieldSymbol || + member is IPropertySymbol || + member is IMethodSymbol) + { + builder.Builder.Append(memberIndentation); + builder.Builder.AppendMemberDefinition(member); + builder.Builder.AppendLine(); + } + else if (member is ITypeSymbol nestedType) + { + var nestedTypeSignature = nestedType.GetSignature(indentLevel, cancellationToken); + builder.Builder.AppendLine(nestedTypeSignature); + } + } + } + + if (type.TypeKind is not TypeKind.Delegate) + { + builder.Builder.Append(indentation); + builder.Builder.Append(ClosingBrace); + } + + return builder.ToStringAndFree(); + } + + /// + /// Returns the file path(s) that contains the definition of the type specified by the supplied + /// . + /// + /// + /// A type's definition may be split across multiple files if the type is a type. + /// + public static async Task> GetDeclarationFilePathsAsync( + this ITypeSymbol type, + Document referencingDocument, + CancellationToken cancellationToken) + { + if (type.Locations.Length is 0) + { + return []; + } + + var builder = PooledHashSet.GetInstance(); + + foreach (var location in type.Locations) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (location.IsInSource && + location.SourceTree.FilePath is { } sourceFilePath && + !string.IsNullOrWhiteSpace(sourceFilePath)) + { + builder.Add(sourceFilePath); + } + else if (location.IsInMetadata && location.MetadataModule?.ContainingAssembly is { } assembly) + { + var referencingProject = referencingDocument.Project; + var compilation = await referencingProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var metadataReference = compilation?.GetMetadataReference(assembly); + + if (metadataReference is PortableExecutableReference portableExecutableReference && + portableExecutableReference.FilePath is { } portableExecutableFilePath && + !string.IsNullOrWhiteSpace(portableExecutableFilePath)) + { + builder.Add(portableExecutableFilePath); + } + else if (metadataReference?.Display is { } filePath && !string.IsNullOrWhiteSpace(filePath)) + { + builder.Add(filePath); + } + } + } + + var filePaths = builder.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); + builder.Free(); + + return filePaths; + } + + /// + /// Returns the of the nearest encompassing of type + /// of which the supplied is a part within the supplied + /// . + /// + public static async Task TryGetSurroundingNodeSpanAsync( + this Document document, + TextSpan span, + CancellationToken cancellationToken) + where T : CSharpSyntaxNode + { + if (document.Project.Language is not LanguageNames.CSharp) + { + return null; + } + + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (model is null) + { + return null; + } + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (root is null) + { + return null; + } + + var containingNode = root.FindNode(span); + var targetNode = containingNode.FirstAncestorOrSelf() ?? containingNode; + + return targetNode.Span; + } + + private static string GetOutermostNamespaceName(this ITypeSymbol type, CancellationToken cancellationToken) + { + var outermostNamespaceName = string.Empty; + var currentNamespace = type.ContainingNamespace; + + while (currentNamespace is not null && !currentNamespace.IsGlobalNamespace) + { + cancellationToken.ThrowIfCancellationRequested(); + + outermostNamespaceName = currentNamespace.Name; + currentNamespace = currentNamespace.ContainingNamespace; + } + + return outermostNamespaceName; + } + + private static bool IsWellKnownType(this ITypeSymbol type, CancellationToken cancellationToken) + => type.GetOutermostNamespaceName(cancellationToken) is "System"; + + private static ImmutableArray RemoveDuplicatesAndPreserveOrder(this ImmutableArray spans) + { + if (spans.IsDefaultOrEmpty || spans.Length is 1) + { + return spans; + } + + if (spans.Length is 2) + { + return spans[0] == spans[1] ? [spans[0]] : spans; + } + + var seen = PooledHashSet.GetInstance(); + var builder = ArrayBuilder.GetInstance(); + + foreach (var span in spans) + { + if (seen.Add(span)) + { + builder.Add(span); + } + } + + seen.Free(); + return builder.ToImmutableAndFree(); + } + + private static bool AddType( + this ArrayBuilder relevantTypes, + ITypeSymbol type, + HashSet seenTypes, + CancellationToken cancellationToken) + { + if (type.TypeKind is TypeKind.Error || !seenTypes.Add(type)) + { + return false; + } + + if (type.ContainingType is { } containingType) + { + return relevantTypes.AddType(containingType, seenTypes, cancellationToken); + } + + if (type is IArrayTypeSymbol arrayType) + { + return relevantTypes.AddType(arrayType.ElementType, seenTypes, cancellationToken); + } + + if (type is INamedTypeSymbol namedType && namedType.IsGenericType) + { + foreach (var typeArgument in namedType.TypeArguments) + { + cancellationToken.ThrowIfCancellationRequested(); + + relevantTypes.AddType(typeArgument, seenTypes, cancellationToken); + } + + if (!namedType.Equals(namedType.ConstructedFrom, SymbolEqualityComparer.Default)) + { + return relevantTypes.AddType(namedType.ConstructedFrom, seenTypes, cancellationToken); + } + } + + if (type.TypeKind is TypeKind.Pointer || + type.TypeKind is TypeKind.TypeParameter || + type.TypeKind is TypeKind.Module || + type.TypeKind is TypeKind.Unknown || + type.SpecialType is not SpecialType.None || + type.IsWellKnownType(cancellationToken)) + { + return false; + } + + relevantTypes.Add(type); + return true; + } + + private static void AddTypeAlongWithBaseTypesAndInterfaces( + this ArrayBuilder relevantTypes, + ITypeSymbol type, + HashSet seenTypes, + CancellationToken cancellationToken) + { + if (!relevantTypes.AddType(type, seenTypes, cancellationToken)) + { + return; + } + + if (type.BaseType is { } baseType) + { + relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces(baseType, seenTypes, cancellationToken); + } + + foreach (var @interface in type.Interfaces) + { + cancellationToken.ThrowIfCancellationRequested(); + + relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces(@interface, seenTypes, cancellationToken); + } + } + + /// + /// Retrieves the s for the types that are referenced within the specified set of spans + /// within a document. + /// + /// + /// + /// Also returns s for related types such as base types and interfaces implemented by the + /// types that are directly referenced in the supplied set of spans. + /// + /// + /// The relative order in which the type references are encountered in the specified set of spans is preserved in + /// the returned collection of s. + /// + /// + public static async Task> GetRelevantTypesAsync( + this Document document, + ImmutableArray spansOfInterest, + CancellationToken cancellationToken) + { + if (document.Project.Language is not LanguageNames.CSharp) + { + return []; + } + + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (model is null) + { + return []; + } + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (root is null) + { + return []; + } + + var relevantTypes = ArrayBuilder.GetInstance(); + var seenTypes = new HashSet(SymbolEqualityComparer.Default); + var seenSpans = PooledHashSet.GetInstance(); + + spansOfInterest = spansOfInterest.RemoveDuplicatesAndPreserveOrder(); + foreach (var spanOfInterest in spansOfInterest) + { + cancellationToken.ThrowIfCancellationRequested(); + + var containingNode = root.FindNode(spanOfInterest); + + var tokensOfInterest = + containingNode + .DescendantTokens() + .SkipWhile(t => t.Span.End < spanOfInterest.Start) + .TakeWhile(t => t.Span.Start <= spanOfInterest.End); + + foreach (var token in tokensOfInterest) + { + cancellationToken.ThrowIfCancellationRequested(); + + var kind = token.Kind(); + + bool isInterestingKind = + kind is SyntaxKind.IdentifierToken || + kind is SyntaxKind.BaseKeyword || + kind is SyntaxKind.ThisKeyword; + + if (isInterestingKind && + spanOfInterest.Contains(token.Span) && + token.Parent is { } node && + seenSpans.Add(token.FullSpan) && + (node.FullSpan == token.FullSpan || seenSpans.Add(node.FullSpan))) + { + if (node is BaseTypeDeclarationSyntax typeSyntax && + model.GetDeclaredSymbol(typeSyntax, cancellationToken) is INamedTypeSymbol declaredType) + { + relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( + declaredType, + seenTypes, + cancellationToken); + } + else if (node is MemberDeclarationSyntax memberSyntax && + memberSyntax.FirstAncestorOrSelf() is { } containingTypeSyntax && + seenSpans.Add(containingTypeSyntax.FullSpan) && + model.GetDeclaredSymbol(containingTypeSyntax, cancellationToken) is INamedTypeSymbol containingDeclaredType) + { + relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( + containingDeclaredType, + seenTypes, + cancellationToken); + } + else if (model.GetTypeInfo(node, cancellationToken).Type is ITypeSymbol typeInfoType) + { + relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( + typeInfoType, + seenTypes, + cancellationToken); + } + else if (model.GetSymbolInfo(node, cancellationToken).Symbol is ITypeSymbol symbolInfoType) + { + relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( + symbolInfoType, + seenTypes, + cancellationToken); + } + } + } + } + + seenSpans.Free(); + return relevantTypes.ToImmutableAndFree(); + } + } +} diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 9cda8eedc3765..52a438595c695 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -9,6 +9,7 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.Remoting.Contexts; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; @@ -153,8 +154,12 @@ private void OnGetSuggestionsCommandExecute() _suggestionsDropdownTelemetry.DropdownButtonClickTimes += 1; } - var allRenameLocations = BaseViewModel.Session.AllRenameLocationsTask.Join(); - ImmutableDictionary context = default; + + + // TODO: fix threading + /* + + if (allRenameLocations.Locations.Count > 0) { var references = new List(); @@ -176,6 +181,9 @@ private void OnGetSuggestionsCommandExecute() contextBuilder.Add("Reference", references.ToArray()); context = contextBuilder.ToImmutableDictionary(); } + */ + var context = BaseViewModel.Session.Context; + _smartRenameSession.PromptOverride = """ Your task is to help a software developer improve the identifier name indicated by [NameThisIdentifier]. The existing identifier name is {identifier} @@ -186,6 +194,7 @@ Your task is to help a software developer improve the identifier name indicated Given the provided information, generate five suggestions to rename the selected symbol. The suggested name should match the style of similar identifiers in the provided [CODE]. Put the suggestions in a JSON array called SuggestedNames and return the json object only as a response. Do not include any markdown formatting. Here are an example of the RESPONSE format: { ""SuggestedNames"": [""..."", ""..."", ""..."", ""..."", ""...""] } """; _getSuggestionsTask = _smartRenameSession.GetSuggestionsAsync(context, _cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); + } } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index 417c3d27b7b75..5285bdeacbcff 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; @@ -22,6 +24,11 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; + public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + { + return Task.FromResult(ImmutableDictionary.Empty); + } + public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) { if (_service != null) diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 5534d677149a0..93d4fb92053af 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -2,11 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; @@ -30,4 +33,14 @@ public async Task GetRenameInfoAsync(Document document, int p return new SymbolInlineRenameInfo( _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } + + public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + { + return GetRenameContextCoreAsync(renameInfo, cancellationToken); + } + + protected virtual Task> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + { + return Task.FromResult(ImmutableDictionary.Empty); + } } diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index c1055e59a9df9..eb4ce7c3c1d5a 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -24,8 +24,8 @@ internal readonly struct InlineRenameLocation public InlineRenameLocation(Document document, TextSpan textSpan) : this() { - this.Document = document; - this.TextSpan = textSpan; + Document = document; + TextSpan = textSpan; } } @@ -72,9 +72,9 @@ internal readonly struct InlineRenameReplacement public InlineRenameReplacement(InlineRenameReplacementKind kind, TextSpan originalSpan, TextSpan newSpan) : this() { - this.Kind = kind; - this.OriginalSpan = originalSpan; - this.NewSpan = newSpan; + Kind = kind; + OriginalSpan = originalSpan; + NewSpan = newSpan; } internal InlineRenameReplacement(RelatedLocation location, TextSpan newSpan) @@ -254,4 +254,6 @@ internal interface IInlineRenameInfo internal interface IEditorInlineRenameService : ILanguageService { Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); + + Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken); } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index 9ef97212c57f6..833734d2373c0 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -90,7 +90,12 @@ public async Task StartInlineSessionAsync( var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( document, textSpan.Start, cancellationToken).ConfigureAwait(false); + var context = await editorRenameService.GetRenameContextAsync(renameInfo, cancellationToken).ConfigureAwait(false); var docComment = symbol.GetDocumentationCommentXml(); + if (!string.IsNullOrWhiteSpace(docComment)) + { + context = context.Add("documentation", new[] { docComment }); + } var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var snapshot = text.FindCorrespondingEditorTextSnapshot(); @@ -115,6 +120,7 @@ public async Task StartInlineSessionAsync( document.Project.Solution.Workspace, renameInfo.TriggerSpan.ToSnapshotSpan(snapshot), renameInfo, + context, options, previewChanges, _uiThreadOperationExecutor, diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index b300558ecd081..48f7b79f247b1 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -89,6 +89,7 @@ private set /// of the rename operation, as determined by the language /// public InlineRenameFileRenameInfo FileRenameInfo { get; } + public ImmutableDictionary Context { get; } /// /// Keep-alive session held alive with the OOP server. This allows us to pin the initial solution snapshot over on @@ -138,6 +139,7 @@ public InlineRenameSession( Workspace workspace, SnapshotSpan triggerSpan, IInlineRenameInfo renameInfo, + ImmutableDictionary context, SymbolRenameOptions options, bool previewChanges, IUIThreadOperationExecutor uiThreadOperationExecutor, @@ -189,6 +191,7 @@ public InlineRenameSession( UndoManager = workspace.Services.GetService(); FileRenameInfo = _renameInfo.GetFileRenameInfo(); + Context = context; // Open a session to oop, syncing our solution to it and pinning it there. The connection will close once // _cancellationTokenSource is canceled (which we always do when the session is finally ended). diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index d2ed503b29202..f1bfe7b397390 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -5,10 +5,10 @@ #nullable disable using System; -using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; @@ -202,6 +202,11 @@ public FSharpEditorInlineRenameService( _service = service; } + public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + { + return Task.FromResult(ImmutableDictionary.Empty); + } + public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) { #pragma warning disable CS0612 // Type or member is obsolete diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index 618fd9df2be78..b6c5953788a99 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,6 +30,11 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } + public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + { + return Task.FromResult(ImmutableDictionary.Empty); + } + public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) { var renameInfo = await _renameService.GetRenameInfoAsync(document, position, cancellationToken).ConfigureAwait(false); From 3516915f2bc117e5f3e8668dc8ba626b11b0fd4b Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 15:37:05 -0700 Subject: [PATCH 07/52] also capture snippet of surrounding method --- .../CSharp/InlineRename/CSharpEditorInlineRenameService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 4fff7edfb7c0a..0b3589a9b0191 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -53,6 +53,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( foreach (var renameLocation in renameLocations.Locations) { var containingStatementOrDeclarationSpan = + await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false); @@ -80,8 +81,8 @@ void AddSpanOfInterest(SourceText documentText, FileLinePositionSpan lineSpan, T { startPosition = surroundingSpanOfInterest.Value.Start; endPosition = surroundingSpanOfInterest.Value.End; - startLine = lineSpan.StartLinePosition.Line; - endLine = lineSpan.EndLinePosition.Line; + startLine = documentText.Lines.GetLineFromPosition(surroundingSpanOfInterest.Value.Start).LineNumber; + endLine = documentText.Lines.GetLineFromPosition(surroundingSpanOfInterest.Value.End).LineNumber; lineCount = endLine - startLine + 1; } From 00a88eca21981d8cd429ae2e393f0da607f2a9b0 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 15:47:37 -0700 Subject: [PATCH 08/52] simplify --- .../CSharpEditorInlineRenameService.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 0b3589a9b0191..1539cb628ba79 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -36,16 +36,13 @@ protected override async Task> GetRenameCo await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false); - var syntaxTree = await renameDefinition.Document.GetSyntaxTreeAsync(cancellationToken); - var textSpan = await syntaxTree?.GetTextAsync(cancellationToken); - var lineSpan = syntaxTree?.GetLineSpan(renameDefinition.SourceSpan, cancellationToken); - - if (lineSpan is null || textSpan is null) + var documentText = await renameDefinition.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (documentText is null) { continue; } - AddSpanOfInterest(textSpan, lineSpan.Value, containingStatementOrDeclarationSpan, definitions); + AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } var renameLocationOptions = new Rename.SymbolRenameOptions(RenameOverloads: true, RenameInStrings: true, RenameInComments: true); @@ -57,16 +54,13 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false); - var syntaxTree = await renameLocation.Document.GetSyntaxTreeAsync(cancellationToken); - var textSpan = await syntaxTree?.GetTextAsync(cancellationToken); - var lineSpan = syntaxTree?.GetLineSpan(renameLocation.TextSpan, cancellationToken); - - if (lineSpan is null || textSpan is null) + var documentText = await renameLocation.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (documentText is null) { continue; } - AddSpanOfInterest(textSpan, lineSpan.Value, containingStatementOrDeclarationSpan, references); + AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references); } var context = ImmutableDictionary.Empty @@ -74,7 +68,7 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(re .Add("reference", references.ToArrayAndFree()); return context; - void AddSpanOfInterest(SourceText documentText, FileLinePositionSpan lineSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder resultBuilder) + void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder resultBuilder) { int startPosition, endPosition, startLine = 0, endLine = 0, lineCount = 0; if (surroundingSpanOfInterest is not null) @@ -90,8 +84,8 @@ void AddSpanOfInterest(SourceText documentText, FileLinePositionSpan lineSpan, T // select a span that encompasses 5 lines above and 5 lines below the error squiggle. if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > 10) { - startLine = Math.Max(0, lineSpan.StartLinePosition.Line - 5); - endLine = Math.Min(documentText.Lines.Count - 1, lineSpan.EndLinePosition.Line + 5); + startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - 5); + endLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + 5); } // If the start and end positions are not at the beginning and end of the start and end lines respectively, From 04f32e4e48d1571040ca0072e6eccb0f76cd74d8 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 15:50:17 -0700 Subject: [PATCH 09/52] fix GetRequiredLanguageService --- .../Core/InlineRename/InlineRenameService.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index 833734d2373c0..12ca728ff22c2 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -86,15 +86,18 @@ public async Task StartInlineSessionAsync( return new InlineRenameSessionInfo(renameInfo.LocalizedErrorMessage); } - var symbolService = document.GetRequiredLanguageService(); - var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( - document, textSpan.Start, cancellationToken).ConfigureAwait(false); - var context = await editorRenameService.GetRenameContextAsync(renameInfo, cancellationToken).ConfigureAwait(false); - var docComment = symbol.GetDocumentationCommentXml(); - if (!string.IsNullOrWhiteSpace(docComment)) + + var symbolService = document.GetLanguageService(); + if (symbolService is not null) { - context = context.Add("documentation", new[] { docComment }); + var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + document, textSpan.Start, cancellationToken).ConfigureAwait(false); + var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); + if (!string.IsNullOrWhiteSpace(docComment)) + { + context = context.Add("documentation", new[] { docComment }); + } } var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); From 8b3f7396230f3abfdaf2617541cd985d1caf53af Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 16:11:08 -0700 Subject: [PATCH 10/52] undo code cleanup artifacts --- .../Core/InlineRename/InlineRenameSession.cs | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 48f7b79f247b1..3532473dedbdb 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -89,6 +89,10 @@ private set /// of the rename operation, as determined by the language /// public InlineRenameFileRenameInfo FileRenameInfo { get; } + + /// + /// Information on references of rename symbol. + /// public ImmutableDictionary Context { get; } /// @@ -104,11 +108,6 @@ private set /// private JoinableTask _allRenameLocationsTask; - /// - /// Expose - /// - internal JoinableTask AllRenameLocationsTask => _allRenameLocationsTask; - /// /// The cancellation token for most work being done by the inline rename session. This /// includes the tasks. @@ -185,10 +184,10 @@ public InlineRenameSession( _previewChanges = previewChanges; _initialRenameText = triggerSpan.GetText(); - ReplacementText = _initialRenameText; + this.ReplacementText = _initialRenameText; _baseSolution = _triggerDocument.Project.Solution; - UndoManager = workspace.Services.GetService(); + this.UndoManager = workspace.Services.GetService(); FileRenameInfo = _renameInfo.GetFileRenameInfo(); Context = context; @@ -254,7 +253,7 @@ private void InitializeOpenBuffers(SnapshotSpan triggerSpan) _triggerView.SetSelection(new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); } - UndoManager.CreateInitialState(ReplacementText, _triggerView.Selection, new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); + this.UndoManager.CreateInitialState(this.ReplacementText, _triggerView.Selection, new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); _openTextBuffers[triggerSpan.Snapshot.TextBuffer].SetReferenceSpans([startingSpan.ToTextSpan()]); UpdateReferenceLocationsTask(); @@ -449,7 +448,7 @@ internal void ApplyReplacementText(string replacementText, bool propagateEditImm { _threadingContext.ThrowIfNotOnUIThread(); VerifyNotDismissed(); - ReplacementText = _renameInfo.GetFinalSymbolName(replacementText); + this.ReplacementText = _renameInfo.GetFinalSymbolName(replacementText); var asyncToken = _asyncListener.BeginAsyncOperation(nameof(ApplyReplacementText)); @@ -520,12 +519,12 @@ private void UpdateConflictResolutionTask() // If the replacement text is empty, we do not update the results of the conflict // resolution task. We instead wait for a non-empty identifier. - if (ReplacementText == string.Empty) + if (this.ReplacementText == string.Empty) { return; } - var replacementText = ReplacementText; + var replacementText = this.ReplacementText; var options = _options; var cancellationToken = _conflictResolutionTaskCancellationSource.Token; @@ -553,7 +552,7 @@ private void QueueApplyReplacements() { // If the replacement text is empty, we do not update the results of the conflict // resolution task. We instead wait for a non-empty identifier. - if (ReplacementText == string.Empty) + if (this.ReplacementText == string.Empty) { return; } @@ -716,7 +715,7 @@ void DismissUIAndRollbackEdits() openBuffer.DisconnectAndRollbackEdits(isClosed); } - UndoManager.Disconnect(); + this.UndoManager.Disconnect(); if (_triggerView != null && !_triggerView.IsClosed) { @@ -761,8 +760,8 @@ private async Task CommitWorkerAsync(bool previewChanges, bool canUseBackg // still 'rename' even if the identifier went away (or was unchanged). But that isn't // a case we're aware of, so it's fine to be opinionated here that we can quickly bail // in these cases. - if (ReplacementText == string.Empty || - ReplacementText == _initialRenameText) + if (this.ReplacementText == string.Empty || + this.ReplacementText == _initialRenameText) { Cancel(); return false; @@ -772,7 +771,7 @@ private async Task CommitWorkerAsync(bool previewChanges, bool canUseBackg try { - if (canUseBackgroundWorkIndicator && RenameService.GlobalOptions.GetOption(InlineRenameSessionOptionsStorage.RenameAsynchronously)) + if (canUseBackgroundWorkIndicator && this.RenameService.GlobalOptions.GetOption(InlineRenameSessionOptionsStorage.RenameAsynchronously)) { // We do not cancel on edit because as part of the rename system we have asynchronous work still // occurring that itself may be asynchronously editing the buffer (for example, updating reference @@ -828,7 +827,7 @@ private async Task CommitCoreAsync(IUIThreadOperationContext operationContext, b newSolution = previewService.PreviewChanges( string.Format(EditorFeaturesResources.Preview_Changes_0, EditorFeaturesResources.Rename), "vs.csharp.refactoring.rename", - string.Format(EditorFeaturesResources.Rename_0_to_1_colon, OriginalSymbolName, ReplacementText), + string.Format(EditorFeaturesResources.Rename_0_to_1_colon, this.OriginalSymbolName, this.ReplacementText), _renameInfo.FullDisplayName, _renameInfo.Glyph, newSolution, @@ -878,7 +877,7 @@ await DismissUIAndRollbackEditsAndEndRenameSessionAsync( using var undoTransaction = _workspace.OpenGlobalUndoTransaction(EditorFeaturesResources.Inline_Rename); - if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, ReplacementText)) + if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, this.ReplacementText)) return (NotificationSeverity.Error, EditorFeaturesResources.Rename_operation_was_cancelled_or_is_not_valid); if (!_workspace.TryApplyChanges(finalSolution)) @@ -905,7 +904,7 @@ await DismissUIAndRollbackEditsAndEndRenameSessionAsync( .SelectMany(c => c.GetChangedDocuments().Concat(c.GetAddedDocuments())) .ToList(); - if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, ReplacementText)) + if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, this.ReplacementText)) return (NotificationSeverity.Information, EditorFeaturesResources.Rename_operation_was_not_properly_completed_Some_file_might_not_have_been_updated); return null; From 14f4c21b9b13c4424148a8bd0a93f2ee87ed3818 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 16:27:17 -0700 Subject: [PATCH 11/52] use ImmutableArray throughout Roslyn's interfaces --- .../CSharpEditorInlineRenameService.cs | 8 ++--- .../UI/SmartRename/SmartRenameViewModel.cs | 34 +++---------------- .../VSTypeScriptEditorInlineRenameService.cs | 4 +-- .../AbstractEditorInlineRenameService.cs | 6 ++-- .../IEditorInlineRenameService.cs | 2 +- .../Core/InlineRename/InlineRenameSession.cs | 2 +- .../XamlEditorInlineRenameService.cs | 4 +-- 7 files changed, 18 insertions(+), 42 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 1539cb628ba79..ef48e626ad491 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -24,7 +24,7 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { - protected override async Task> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + protected override async Task>> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) { var seen = PooledHashSet.GetInstance(); var definitions = ArrayBuilder.GetInstance(); @@ -63,9 +63,9 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(re AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references); } - var context = ImmutableDictionary.Empty - .Add("definition", definitions.ToArrayAndFree()) - .Add("reference", references.ToArrayAndFree()); + var context = ImmutableDictionary>.Empty + .Add("definition", definitions.ToImmutableAndFree()) + .Add("reference", references.ToImmutableAndFree()); return context; void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder resultBuilder) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 52a438595c695..c93dc24535c46 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -154,36 +154,12 @@ private void OnGetSuggestionsCommandExecute() _suggestionsDropdownTelemetry.DropdownButtonClickTimes += 1; } + // TODO: Context should be lazily evaluated now. + var context = ImmutableDictionary.CreateRange( + BaseViewModel.Session.Context + .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); - - // TODO: fix threading - /* - - - if (allRenameLocations.Locations.Count > 0) - { - var references = new List(); - foreach (var renameLocation in allRenameLocations.Locations) - { - var syntaxTree = renameLocation.Document.GetSyntaxTreeSynchronously(_cancellationTokenSource.Token); - var text = syntaxTree.GetText(_cancellationTokenSource.Token); - var lineSpan = syntaxTree.GetLineSpan(renameLocation.TextSpan, _cancellationTokenSource.Token); - - var startLine = lineSpan.StartLinePosition.Line; - var endLine = lineSpan.EndLinePosition.Line; - - var documentContent = string.Join( - Environment.NewLine, - text.Lines.Skip(startLine).Take(endLine - startLine + 1).Select(l => l.ToString())); - references.Add(documentContent); - } - var contextBuilder = ImmutableDictionary.CreateBuilder(); - contextBuilder.Add("Reference", references.ToArray()); - context = contextBuilder.ToImmutableDictionary(); - } - */ - var context = BaseViewModel.Session.Context; - + // TODO: This is for local prototyping. _smartRenameSession.PromptOverride = """ Your task is to help a software developer improve the identifier name indicated by [NameThisIdentifier]. The existing identifier name is {identifier} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index 5285bdeacbcff..5daf418916068 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -24,9 +24,9 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; - public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) { - return Task.FromResult(ImmutableDictionary.Empty); + return Task.FromResult(ImmutableDictionary>.Empty); } public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 93d4fb92053af..8cd3bedc50d55 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -34,13 +34,13 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } - public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) { return GetRenameContextCoreAsync(renameInfo, cancellationToken); } - protected virtual Task> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + protected virtual Task>> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) { - return Task.FromResult(ImmutableDictionary.Empty); + return Task.FromResult(ImmutableDictionary>.Empty); } } diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index eb4ce7c3c1d5a..9e7c7d363084b 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -255,5 +255,5 @@ internal interface IEditorInlineRenameService : ILanguageService { Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); - Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken); + Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken); } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 3532473dedbdb..34d1e79ca65b3 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -93,7 +93,7 @@ private set /// /// Information on references of rename symbol. /// - public ImmutableDictionary Context { get; } + public ImmutableDictionary> Context { get; } /// /// Keep-alive session held alive with the OOP server. This allows us to pin the initial solution snapshot over on diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index b6c5953788a99..8b03202d86f81 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,9 +30,9 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } - public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) { - return Task.FromResult(ImmutableDictionary.Empty); + return Task.FromResult(ImmutableDictionary>.Empty); } public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) From 9a664db2ced7ebd1ccd70918b56f4f56ae1e5a78 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 12 Jun 2024 16:35:12 -0700 Subject: [PATCH 12/52] fixup --- src/EditorFeatures/Core/InlineRename/InlineRenameService.cs | 3 ++- src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs | 2 +- .../FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index 12ca728ff22c2..7ddeea3f79cab 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; @@ -96,7 +97,7 @@ public async Task StartInlineSessionAsync( var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); if (!string.IsNullOrWhiteSpace(docComment)) { - context = context.Add("documentation", new[] { docComment }); + context = context.Add("documentation", ImmutableArray.Empty.Add(docComment)); } } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 34d1e79ca65b3..08efaaca44126 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -138,7 +138,7 @@ public InlineRenameSession( Workspace workspace, SnapshotSpan triggerSpan, IInlineRenameInfo renameInfo, - ImmutableDictionary context, + ImmutableDictionary> context, SymbolRenameOptions options, bool previewChanges, IUIThreadOperationExecutor uiThreadOperationExecutor, diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index f1bfe7b397390..7332f45338b2a 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,9 +202,9 @@ public FSharpEditorInlineRenameService( _service = service; } - public Task> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) { - return Task.FromResult(ImmutableDictionary.Empty); + return Task.FromResult(ImmutableDictionary>.Empty); } public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) From 2eb5b30e5747828223781152861bd615176179bb Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 18 Jun 2024 12:27:51 -0700 Subject: [PATCH 13/52] refactor to get context only if smart rename is active --- .../UI/SmartRename/SmartRenameViewModel.cs | 41 ++++++++++++------- .../Lightup/ISmartRenameSessionWrapper.cs | 3 -- .../Core/InlineRename/InlineRenameService.cs | 8 ---- .../Core/InlineRename/InlineRenameSession.cs | 10 ++--- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index c93dc24535c46..0fbad4e9279cd 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -13,12 +13,15 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Input; +using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Editor.InlineRename; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.EditorFeatures.Lightup; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.PlatformUI; @@ -154,24 +157,34 @@ private void OnGetSuggestionsCommandExecute() _suggestionsDropdownTelemetry.DropdownButtonClickTimes += 1; } - // TODO: Context should be lazily evaluated now. - var context = ImmutableDictionary.CreateRange( - BaseViewModel.Session.Context - .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); - - // TODO: This is for local prototyping. - _smartRenameSession.PromptOverride = """ - Your task is to help a software developer improve the identifier name indicated by [NameThisIdentifier]. The existing identifier name is {identifier} + _getSuggestionsTask = GetSuggestionsTask(_cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); + } + } - Use the following information as context: + private async Task GetSuggestionsTask(CancellationToken cancellationToken) + { + var document = this.BaseViewModel.Session.TriggerDocument; + var editorRenameService = document.GetRequiredLanguageService(); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, cancellationToken); - {context} + var symbolService = document.GetLanguageService(); + if (symbolService is not null) + { + var textSpan = this.BaseViewModel.Session.RenameInfo.TriggerSpan; + var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + document, textSpan.Start, cancellationToken); + var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); + if (!string.IsNullOrWhiteSpace(docComment)) + { + context = context.Add("documentation", ImmutableArray.Empty.Add(docComment)); + } + } - Given the provided information, generate five suggestions to rename the selected symbol. The suggested name should match the style of similar identifiers in the provided [CODE]. Put the suggestions in a JSON array called SuggestedNames and return the json object only as a response. Do not include any markdown formatting. Here are an example of the RESPONSE format: { ""SuggestedNames"": [""..."", ""..."", ""..."", ""..."", ""...""] } - """; - _getSuggestionsTask = _smartRenameSession.GetSuggestionsAsync(context, _cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); + var smartRenameContext = ImmutableDictionary.CreateRange( + context + .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); - } + _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken); } private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs index f450b711208b8..0be4f0b3a51e4 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/ISmartRenameSessionWrapper.cs @@ -25,7 +25,6 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup; private static readonly Func s_isInProgressAccessor; private static readonly Func s_statusMessageAccessor; private static readonly Func s_statusMessageVisibilityAccessor; - private static readonly Action s_promptOverrideSetter; private static readonly Func> s_suggestedNamesAccessor; private static readonly Func>> s_getSuggestionsAsync; @@ -44,7 +43,6 @@ static ISmartRenameSessionWrapper() s_isInProgressAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(IsInProgress), false); s_statusMessageAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(StatusMessage), ""); s_statusMessageVisibilityAccessor = LightupHelpers.CreatePropertyAccessor(s_wrappedType, nameof(StatusMessageVisibility), false); - s_promptOverrideSetter = LightupHelpers.CreatePropertySetter(s_wrappedType, nameof(PromptOverride), typeof(string)); s_suggestedNamesAccessor = LightupHelpers.CreatePropertyAccessor>(s_wrappedType, nameof(SuggestedNames), []); s_getSuggestionsAsync = LightupHelpers.CreateFunctionAccessor>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList()); @@ -63,7 +61,6 @@ private ISmartRenameSessionWrapper(object instance) public bool IsInProgress => s_isInProgressAccessor(_instance); public string StatusMessage => s_statusMessageAccessor(_instance); public bool StatusMessageVisibility => s_statusMessageVisibilityAccessor(_instance); - public string PromptOverride { set { s_promptOverrideSetter(_instance, value); } } public IReadOnlyList SuggestedNames => s_suggestedNamesAccessor(_instance); public event PropertyChangedEventHandler PropertyChanged diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index 7ddeea3f79cab..1440f8d28bcf2 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -87,18 +87,11 @@ public async Task StartInlineSessionAsync( return new InlineRenameSessionInfo(renameInfo.LocalizedErrorMessage); } - var context = await editorRenameService.GetRenameContextAsync(renameInfo, cancellationToken).ConfigureAwait(false); - var symbolService = document.GetLanguageService(); if (symbolService is not null) { var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( document, textSpan.Start, cancellationToken).ConfigureAwait(false); - var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); - if (!string.IsNullOrWhiteSpace(docComment)) - { - context = context.Add("documentation", ImmutableArray.Empty.Add(docComment)); - } } var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); @@ -124,7 +117,6 @@ public async Task StartInlineSessionAsync( document.Project.Solution.Workspace, renameInfo.TriggerSpan.ToSnapshotSpan(snapshot), renameInfo, - context, options, previewChanges, _uiThreadOperationExecutor, diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 08efaaca44126..03ce6a9225d88 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -55,6 +55,8 @@ internal partial class InlineRenameSession : IInlineRenameSession, IFeatureContr private readonly IThreadingContext _threadingContext; public readonly InlineRenameService RenameService; + internal Document TriggerDocument => _triggerDocument; + private bool _dismissed; private bool _isApplyingEdit; private string _replacementText; @@ -90,11 +92,6 @@ private set /// public InlineRenameFileRenameInfo FileRenameInfo { get; } - /// - /// Information on references of rename symbol. - /// - public ImmutableDictionary> Context { get; } - /// /// Keep-alive session held alive with the OOP server. This allows us to pin the initial solution snapshot over on /// the oop side, which is valuable for preventing it from constantly being dropped/synced on every conflict @@ -126,6 +123,7 @@ private set private CancellationTokenSource _conflictResolutionTaskCancellationSource = new CancellationTokenSource(); private readonly IInlineRenameInfo _renameInfo; + internal IInlineRenameInfo RenameInfo => _renameInfo; /// /// The initial text being renamed. @@ -138,7 +136,6 @@ public InlineRenameSession( Workspace workspace, SnapshotSpan triggerSpan, IInlineRenameInfo renameInfo, - ImmutableDictionary> context, SymbolRenameOptions options, bool previewChanges, IUIThreadOperationExecutor uiThreadOperationExecutor, @@ -190,7 +187,6 @@ public InlineRenameSession( this.UndoManager = workspace.Services.GetService(); FileRenameInfo = _renameInfo.GetFileRenameInfo(); - Context = context; // Open a session to oop, syncing our solution to it and pinning it there. The connection will close once // _cancellationTokenSource is canceled (which we always do when the session is finally ended). From 6b3e0a45c6bd7d5a29d810b28b2092216da9b035 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 18 Jun 2024 12:30:10 -0700 Subject: [PATCH 14/52] revert the now unnecessary change --- .../Core.Wpf/Lightup/LightupHelpers.cs | 155 ------------------ 1 file changed, 155 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index 06418ba4010b0..6fda65becc9c9 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Microsoft.Build.Framework.XamlTypes; namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup; @@ -97,75 +96,6 @@ public static Func CreatePropertyAccessor(Type? type, st return expression.Compile(); } - /// - /// Generates a compiled setter method for a property which cannot be bound at compile time. - /// - /// The compile-time type representing the instance on which the property is defined. This - /// may be a superclass of the actual type on which the property is declared if the declaring type is not - /// available at compile time. - /// The compile-type type representing the type of the property. This may be a - /// superclass of the actual type of the property if the property type is not available at compile - /// time. - /// The runtime time on which the property is defined. - /// The name of the property to access. - /// The value to set. - /// An accessor method to access the specified runtime property. - public static Action CreatePropertySetter(Type? type, string propertyName, Type valueType) - { - if (propertyName is null) - { - throw new ArgumentNullException(nameof(propertyName)); - } - - if (type == null) - { - throw new NotImplementedException(); - //return CreateFallbackAccessor(defaultValue); - } - - if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - { - throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'"); - } - - var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); - if (property == null) - { - throw new NotImplementedException(); - //return CreateFallbackAccessor(defaultValue); - } - - var parameters = property.GetSetMethod().GetParameters(); - if (valueType != parameters[0].ParameterType) - { - throw new ArgumentException($"Type '{valueType}' was expected to match parameter type '{parameters[0].ParameterType}'", nameof(valueType)); - } - - if (!typeof(TValue).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) - { - throw new InvalidOperationException($"Property '{property}' produces a value of type '{property.PropertyType}', which is not assignable to type '{typeof(TValue)}'"); - } - - var instanceParameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T))); - var instance = - type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) - ? (Expression)instanceParameter - : Expression.Convert(instanceParameter, type); - - var parameter = Expression.Parameter(typeof(TValue), parameters[0].Name); - var convertedArgument = - valueType.GetTypeInfo().IsAssignableFrom(typeof(TValue).GetTypeInfo()) - ? (Expression)parameter - : Expression.Convert(parameter, valueType); - - var expression = - Expression.Lambda>( - Expression.Call(instance, property.SetMethod, convertedArgument), - instanceParameter, - parameter); - return expression.Compile(); - } - /// /// Generates a compiled accessor method for a method with a signature compatible with which /// cannot be bound at compile time. @@ -422,91 +352,6 @@ public static Func CreateFunctionAccessor(Ty return expression.Compile(); } - /// - /// Generates a compiled accessor method for a property which cannot be bound at compile time. - /// - /// The compile-time type representing the instance on which the property is defined. This - /// may be a superclass of the actual type on which the property is declared if the declaring type is not - /// available at compile time. - /// The compile-time type representing the type of the first argument. This - /// may be a superclass of the actual type of the argument if the declared type is not available at compile - /// time. - /// The compile-type type representing the result of the property. This may be a - /// superclass of the actual type of the property if the property type is not available at compile - /// time. - /// The runtime time on which the property is defined. If this value is null, the runtime - /// time is assumed to not exist, and a fallback accessor returning will be - /// generated. - /// The name of the method to access. - /// The value to return if the method is not available at runtime. - /// An accessor method to access the specified runtime property. - public static Func CreateFunctionAccessor(Type? type, string methodName, Type? arg0Type, Type? arg1Type, TResult defaultValue) - { - if (methodName is null) - { - throw new ArgumentNullException(nameof(methodName)); - } - - if (type == null) - { - throw new NotImplementedException(); - //return CreateFallbackFunction(defaultValue); - } - - if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - { - throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'"); - } - - var method = type.GetTypeInfo().GetDeclaredMethods(methodName).Single(method => - { - var parameters = method.GetParameters(); - return parameters is [{ ParameterType: var parameter0Type }, { ParameterType: var parameter1Type }] && parameter0Type == arg0Type && parameter1Type == arg1Type; - }); - - var parameters = method.GetParameters(); - if (arg0Type != parameters[0].ParameterType) - { - throw new ArgumentException($"Type '{arg0Type}' was expected to match parameter type '{parameters[0].ParameterType}'", nameof(arg0Type)); - } - if (arg1Type != parameters[1].ParameterType) - { - throw new ArgumentException($"Type '{arg1Type}' was expected to match parameter type '{parameters[1].ParameterType}'", nameof(arg1Type)); - } - - if (!typeof(TResult).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) - { - throw new InvalidOperationException($"Method '{method}' produces a value of type '{method.ReturnType}', which is not assignable to type '{typeof(TResult)}'"); - } - - var parameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T))); - var argument0 = Expression.Parameter(typeof(TArg0), parameters[0].Name); - var argument1 = Expression.Parameter(typeof(TArg1), parameters[1].Name); - var instance = - type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) - ? (Expression)parameter - : Expression.Convert(parameter, type); - var convertedArgument0 = - arg0Type.GetTypeInfo().IsAssignableFrom(typeof(TArg0).GetTypeInfo()) - ? (Expression)argument0 - : Expression.Convert(argument0, arg0Type); - var convertedArgument1 = - arg1Type.GetTypeInfo().IsAssignableFrom(typeof(TArg1).GetTypeInfo()) - ? (Expression)argument1 - : Expression.Convert(argument1, arg1Type); - - var expression = - Expression.Lambda>( - Expression.Convert( - Expression.Call( - instance, - method, - convertedArgument0, convertedArgument1), typeof(TResult)), - parameter, - argument0, argument1); - return expression.Compile(); - } - private static string GenerateParameterName(Type parameterType) { var typeName = parameterType.Name; From 3217dbe11d5ac2434b0eabfa7712762034333a76 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 18 Jun 2024 16:23:08 -0700 Subject: [PATCH 15/52] restore CreateFunctionAccessor with 2 parameters --- .../Core.Wpf/Lightup/LightupHelpers.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index 6fda65becc9c9..000b179a7b2c3 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -352,6 +352,97 @@ public static Func CreateFunctionAccessor(Ty return expression.Compile(); } + /// + /// Generates a compiled accessor method for a property which cannot be bound at compile time. + /// + /// The compile-time type representing the instance on which the property is defined. This + /// may be a superclass of the actual type on which the property is declared if the declaring type is not + /// available at compile time. + /// The compile-time type representing the type of the first argument. This + /// may be a superclass of the actual type of the argument if the declared type is not available at compile + /// time. + /// The compile-time type representing the type of the second argument. This + /// may be a superclass of the actual type of the argument if the declared type is not available at compile + /// time. + /// The compile-type type representing the result of the function. This may be a + /// superclass of the actual return type of the function if the return type is not available at compile + /// time. + /// The runtime time on which the first argument is defined. If this value is null, the runtime + /// time is assumed to not exist, and a fallback accessor returning will be + /// generated. + /// The runtime time on which the second argument is defined. If this value is null, the runtime + /// time is assumed to not exist, and a fallback accessor returning will be + /// generated. + /// The name of the method to access. + /// The value to return if the method is not available at runtime. + /// An accessor method to access the specified runtime property. + public static Func CreateFunctionAccessor(Type? type, string methodName, Type? arg0Type, Type? arg1Type, TResult defaultValue) + { + if (methodName is null) + { + throw new ArgumentNullException(nameof(methodName)); + } + + if (type == null) + { + return CreateFallbackFunction(defaultValue); + } + + if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'"); + } + + var method = type.GetTypeInfo().GetDeclaredMethods(methodName).Single(method => + { + var parameters = method.GetParameters(); + return parameters is [{ ParameterType: var parameter0Type }, { ParameterType: var parameter1Type }] && parameter0Type == arg0Type && parameter1Type == arg1Type; + }); + + var parameters = method.GetParameters(); + if (arg0Type != parameters[0].ParameterType) + { + throw new ArgumentException($"Type '{arg0Type}' was expected to match parameter type '{parameters[0].ParameterType}'", nameof(arg0Type)); + } + if (arg1Type != parameters[1].ParameterType) + { + throw new ArgumentException($"Type '{arg1Type}' was expected to match parameter type '{parameters[1].ParameterType}'", nameof(arg1Type)); + } + + if (!typeof(TResult).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) + { + throw new InvalidOperationException($"Method '{method}' produces a value of type '{method.ReturnType}', which is not assignable to type '{typeof(TResult)}'"); + } + + var parameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T))); + var argument0 = Expression.Parameter(typeof(TArg0), parameters[0].Name); + var argument1 = Expression.Parameter(typeof(TArg1), parameters[1].Name); + var instance = + type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) + ? (Expression)parameter + : Expression.Convert(parameter, type); + var convertedArgument0 = + arg0Type.GetTypeInfo().IsAssignableFrom(typeof(TArg0).GetTypeInfo()) + ? (Expression)argument0 + : Expression.Convert(argument0, arg0Type); + var convertedArgument1 = + arg1Type.GetTypeInfo().IsAssignableFrom(typeof(TArg1).GetTypeInfo()) + ? (Expression)argument1 + : Expression.Convert(argument1, arg1Type); + + var expression = + Expression.Lambda>( + Expression.Convert( + Expression.Call( + instance, + method, + convertedArgument0, convertedArgument1), typeof(TResult)), + parameter, + argument0, argument1); + return expression.Compile(); + } + + private static string GenerateParameterName(Type parameterType) { var typeName = parameterType.Name; @@ -438,4 +529,21 @@ TResult FallbackFunction(T instance, TArg arg) return FallbackFunction; } + + private static Func CreateFallbackFunction(TResult defaultValue) + { + TResult FallbackFunction(T instance, TArg0 arg0, TArg1 arg1) + { + if (instance == null) + { + // Unlike an extension method which would throw ArgumentNullException here, the light-up + // behavior needs to match behavior of the underlying property. + throw new NullReferenceException(); + } + + return defaultValue; + } + + return FallbackFunction; + } } From e6c0de890ba6eed815ddfe1ada95dba7d19c3c6e Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 18 Jun 2024 16:23:27 -0700 Subject: [PATCH 16/52] Dont duplicate work of getting rename locations --- .../InlineRename/CSharpEditorInlineRenameService.cs | 8 ++++---- .../InlineRename/UI/SmartRename/SmartRenameViewModel.cs | 2 +- .../VSTypeScript/VSTypeScriptEditorInlineRenameService.cs | 2 +- .../InlineRename/AbstractEditorInlineRenameService.cs | 6 +++--- .../Core/InlineRename/IEditorInlineRenameService.cs | 3 ++- .../Core/InlineRename/InlineRenameSession.cs | 2 ++ .../Internal/Editor/FSharpEditorInlineRenameService.cs | 2 +- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index ef48e626ad491..502435511f54b 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -24,13 +25,13 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { - protected override async Task>> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + protected override async Task>> GetRenameContextCoreAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) { var seen = PooledHashSet.GetInstance(); var definitions = ArrayBuilder.GetInstance(); var references = ArrayBuilder.GetInstance(); - foreach (var renameDefinition in renameInfo.DefinitionLocations) + foreach (var renameDefinition in renameSession.RenameInfo.DefinitionLocations) { var containingStatementOrDeclarationSpan = await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? @@ -45,8 +46,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } - var renameLocationOptions = new Rename.SymbolRenameOptions(RenameOverloads: true, RenameInStrings: true, RenameInComments: true); - var renameLocations = await renameInfo.FindRenameLocationsAsync(renameLocationOptions, cancellationToken); + var renameLocations = await renameSession.AllRenameLocationsTask.JoinAsync(cancellationToken).ConfigureAwait(false); foreach (var renameLocation in renameLocations.Locations) { var containingStatementOrDeclarationSpan = diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 0fbad4e9279cd..9a75958222153 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -165,7 +165,7 @@ private async Task GetSuggestionsTask(CancellationToken cancellationToken) { var document = this.BaseViewModel.Session.TriggerDocument; var editorRenameService = document.GetRequiredLanguageService(); - var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, cancellationToken); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session, cancellationToken); var symbolService = document.GetLanguageService(); if (symbolService is not null) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index 5daf418916068..afb140f892869 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -24,7 +24,7 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; - public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 8cd3bedc50d55..d93bcdbb82965 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -34,12 +34,12 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } - public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) { - return GetRenameContextCoreAsync(renameInfo, cancellationToken); + return GetRenameContextCoreAsync(renameSession, cancellationToken); } - protected virtual Task>> GetRenameContextCoreAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + protected virtual Task>> GetRenameContextCoreAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index 9e7c7d363084b..a7a7b6d47b64e 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Rename.ConflictEngine; @@ -255,5 +256,5 @@ internal interface IEditorInlineRenameService : ILanguageService { Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); - Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken); + Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken); } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 03ce6a9225d88..0f46eedebf25f 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -123,7 +123,9 @@ private set private CancellationTokenSource _conflictResolutionTaskCancellationSource = new CancellationTokenSource(); private readonly IInlineRenameInfo _renameInfo; + internal IInlineRenameInfo RenameInfo => _renameInfo; + internal JoinableTask AllRenameLocationsTask => _allRenameLocationsTask; /// /// The initial text being renamed. diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index 7332f45338b2a..2bec2d698c3a2 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,7 +202,7 @@ public FSharpEditorInlineRenameService( _service = service; } - public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } From 7b3c5d2b7c449a879f1e1143cb186dca00b2d663 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 18 Jun 2024 16:48:13 -0700 Subject: [PATCH 17/52] use interface in the method declarations --- .../InlineRename/CSharpEditorInlineRenameService.cs | 12 ++++++++---- .../VSTypeScriptEditorInlineRenameService.cs | 2 +- .../AbstractEditorInlineRenameService.cs | 6 ++---- .../Core/InlineRename/IEditorInlineRenameService.cs | 2 +- .../Editor/FSharpEditorInlineRenameService.cs | 2 +- .../InlineRename/XamlEditorInlineRenameService.cs | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 502435511f54b..716800cd13224 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; -using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -25,13 +24,18 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { - protected override async Task>> GetRenameContextCoreAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) + protected override async Task>> GetRenameContextCoreAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) { var seen = PooledHashSet.GetInstance(); var definitions = ArrayBuilder.GetInstance(); var references = ArrayBuilder.GetInstance(); - foreach (var renameDefinition in renameSession.RenameInfo.DefinitionLocations) + if (renameSession is not InlineRenameSession session) + { + return ImmutableDictionary>.Empty; + } + + foreach (var renameDefinition in session.RenameInfo.DefinitionLocations) { var containingStatementOrDeclarationSpan = await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? @@ -46,7 +50,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } - var renameLocations = await renameSession.AllRenameLocationsTask.JoinAsync(cancellationToken).ConfigureAwait(false); + var renameLocations = await session.AllRenameLocationsTask.JoinAsync(cancellationToken).ConfigureAwait(false); foreach (var renameLocation in renameLocations.Locations) { var containingStatementOrDeclarationSpan = diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index afb140f892869..aedc1e64405d6 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -24,7 +24,7 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; - public Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index d93bcdbb82965..83682cf602650 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -2,14 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; @@ -34,12 +32,12 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } - public Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) { return GetRenameContextCoreAsync(renameSession, cancellationToken); } - protected virtual Task>> GetRenameContextCoreAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) + protected virtual Task>> GetRenameContextCoreAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index a7a7b6d47b64e..3ef17fe825529 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -256,5 +256,5 @@ internal interface IEditorInlineRenameService : ILanguageService { Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); - Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken); + Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index 2bec2d698c3a2..c715425f947f2 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,7 +202,7 @@ public FSharpEditorInlineRenameService( _service = service; } - public Task>> GetRenameContextAsync(InlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index 8b03202d86f81..e6b1c59e077df 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,7 +30,7 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } - public Task>> GetRenameContextAsync(IInlineRenameInfo renameInfo, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } From 0aced56847e29b3ac16e50f4beaea9561a169a39 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 21 Jun 2024 11:20:39 -0700 Subject: [PATCH 18/52] fix warning --- .../UI/SmartRename/SmartRenameViewModel.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 9a75958222153..0defd2a1e1494 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -157,26 +157,28 @@ private void OnGetSuggestionsCommandExecute() _suggestionsDropdownTelemetry.DropdownButtonClickTimes += 1; } - _getSuggestionsTask = GetSuggestionsTask(_cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); + _getSuggestionsTask = GetSuggestionsTaskAsync(_cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); } } - private async Task GetSuggestionsTask(CancellationToken cancellationToken) + private async Task GetSuggestionsTaskAsync(CancellationToken cancellationToken) { var document = this.BaseViewModel.Session.TriggerDocument; var editorRenameService = document.GetRequiredLanguageService(); - var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session, cancellationToken); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session, cancellationToken) + .ConfigureAwait(true); var symbolService = document.GetLanguageService(); if (symbolService is not null) { var textSpan = this.BaseViewModel.Session.RenameInfo.TriggerSpan; var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( - document, textSpan.Start, cancellationToken); + document, textSpan.Start, cancellationToken) + .ConfigureAwait(true); var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); if (!string.IsNullOrWhiteSpace(docComment)) { - context = context.Add("documentation", ImmutableArray.Empty.Add(docComment)); + context = context.Add("documentation", ImmutableArray.Empty.Add(docComment!)); } } @@ -184,7 +186,8 @@ private async Task GetSuggestionsTask(CancellationToken cancellationToken) context .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); - _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken); + _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken) + .ConfigureAwait(true); } private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) From 5f483e960aa25dd110b9c881a83dea3e6457b3d2 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 21 Jun 2024 15:00:34 -0700 Subject: [PATCH 19/52] remove IInlineRenameSession from the API --- .../InlineRename/CSharpEditorInlineRenameService.cs | 12 +++--------- .../UI/SmartRename/SmartRenameViewModel.cs | 4 +++- .../VSTypeScriptEditorInlineRenameService.cs | 2 +- .../AbstractEditorInlineRenameService.cs | 6 +++--- .../Core/InlineRename/IEditorInlineRenameService.cs | 5 ++++- .../Editor/FSharpEditorInlineRenameService.cs | 2 +- .../InlineRename/XamlEditorInlineRenameService.cs | 2 +- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 716800cd13224..9e3dcf280bd96 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -24,18 +24,13 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { - protected override async Task>> GetRenameContextCoreAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) + protected override async Task>> GetRenameContextCoreAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { var seen = PooledHashSet.GetInstance(); var definitions = ArrayBuilder.GetInstance(); var references = ArrayBuilder.GetInstance(); - if (renameSession is not InlineRenameSession session) - { - return ImmutableDictionary>.Empty; - } - - foreach (var renameDefinition in session.RenameInfo.DefinitionLocations) + foreach (var renameDefinition in inlineRenameInfo.DefinitionLocations) { var containingStatementOrDeclarationSpan = await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? @@ -50,8 +45,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } - var renameLocations = await session.AllRenameLocationsTask.JoinAsync(cancellationToken).ConfigureAwait(false); - foreach (var renameLocation in renameLocations.Locations) + foreach (var renameLocation in inlineRenameLocationSet.Locations) { var containingStatementOrDeclarationSpan = await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 0defd2a1e1494..2ed7790f9df20 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -165,7 +165,9 @@ private async Task GetSuggestionsTaskAsync(CancellationToken cancellationToken) { var document = this.BaseViewModel.Session.TriggerDocument; var editorRenameService = document.GetRequiredLanguageService(); - var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session, cancellationToken) + var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) + .ConfigureAwait(true); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) .ConfigureAwait(true); var symbolService = document.GetLanguageService(); diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index aedc1e64405d6..4f88d81441d64 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -24,7 +24,7 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; - public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 83682cf602650..14b4bd1e510bc 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -32,12 +32,12 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } - public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { - return GetRenameContextCoreAsync(renameSession, cancellationToken); + return GetRenameContextCoreAsync(inlineRenameInfo, inlineRenameLocationSet, cancellationToken); } - protected virtual Task>> GetRenameContextCoreAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) + protected virtual Task>> GetRenameContextCoreAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index 3ef17fe825529..6f320f649955d 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -256,5 +256,8 @@ internal interface IEditorInlineRenameService : ILanguageService { Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); - Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken); + Task>> GetRenameContextAsync( + IInlineRenameInfo inlineRenameInfo, + IInlineRenameLocationSet inlineRenameLocationSet, + CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index c715425f947f2..e6c8670ed7732 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,7 +202,7 @@ public FSharpEditorInlineRenameService( _service = service; } - public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index e6b1c59e077df..215018e9044bd 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,7 +30,7 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } - public Task>> GetRenameContextAsync(IInlineRenameSession renameSession, CancellationToken cancellationToken) + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } From 7d6f4ea9f13b990dea82ab2073e8e100de6a5596 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 21 Jun 2024 15:25:44 -0700 Subject: [PATCH 20/52] cleanup --- .../Core/InlineRename/InlineRenameService.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index 1440f8d28bcf2..7697a502b5e02 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InlineRename; using Microsoft.CodeAnalysis.Navigation; @@ -87,13 +85,6 @@ public async Task StartInlineSessionAsync( return new InlineRenameSessionInfo(renameInfo.LocalizedErrorMessage); } - var symbolService = document.GetLanguageService(); - if (symbolService is not null) - { - var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( - document, textSpan.Start, cancellationToken).ConfigureAwait(false); - } - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var snapshot = text.FindCorrespondingEditorTextSnapshot(); Contract.ThrowIfNull(snapshot, "The document used for starting the inline rename session should still be open and associated with a snapshot."); From fc7c0c75088bff9c274c6b320aa28fe3c79dce4e Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 21 Jun 2024 15:30:36 -0700 Subject: [PATCH 21/52] refactor getting doccomments --- .../CSharpEditorInlineRenameService.cs | 34 ++++++++++++++++--- .../UI/SmartRename/SmartRenameViewModel.cs | 16 +-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 9e3dcf280bd96..7458a8d083733 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -10,9 +10,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.CSharp.InlineRename; @@ -29,6 +30,7 @@ protected override async Task var seen = PooledHashSet.GetInstance(); var definitions = ArrayBuilder.GetInstance(); var references = ArrayBuilder.GetInstance(); + var docComments = ArrayBuilder.GetInstance(); foreach (var renameDefinition in inlineRenameInfo.DefinitionLocations) { @@ -42,6 +44,20 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( continue; } + var symbolService = renameDefinition.Document.GetLanguageService(); + if (symbolService is not null) + { + var textSpan = inlineRenameInfo.TriggerSpan; + var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + renameDefinition.Document, textSpan.Start, cancellationToken) + .ConfigureAwait(true); + var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); + if (!string.IsNullOrWhiteSpace(docComment)) + { + docComments.Add(docComment!); + } + } + AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } @@ -61,9 +77,19 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(re AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references); } - var context = ImmutableDictionary>.Empty - .Add("definition", definitions.ToImmutableAndFree()) - .Add("reference", references.ToImmutableAndFree()); + var context = ImmutableDictionary>.Empty; + if (!definitions.IsEmpty) + { + context = context.Add("definition", definitions.ToImmutableAndFree()); + } + if (!references.IsEmpty) + { + context = context.Add("reference", references.ToImmutableAndFree()); + } + if (!docComments.IsEmpty) + { + context = context.Add("documentation", docComments.ToImmutableAndFree()); + } return context; void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder resultBuilder) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 2ed7790f9df20..c733699bfae03 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -164,26 +164,12 @@ private void OnGetSuggestionsCommandExecute() private async Task GetSuggestionsTaskAsync(CancellationToken cancellationToken) { var document = this.BaseViewModel.Session.TriggerDocument; + _ = document.GetLanguageService(); var editorRenameService = document.GetRequiredLanguageService(); var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) .ConfigureAwait(true); var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) .ConfigureAwait(true); - - var symbolService = document.GetLanguageService(); - if (symbolService is not null) - { - var textSpan = this.BaseViewModel.Session.RenameInfo.TriggerSpan; - var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( - document, textSpan.Start, cancellationToken) - .ConfigureAwait(true); - var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); - if (!string.IsNullOrWhiteSpace(docComment)) - { - context = context.Add("documentation", ImmutableArray.Empty.Add(docComment!)); - } - } - var smartRenameContext = ImmutableDictionary.CreateRange( context .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); From 8e7719a2822f4bbe8bb464fdf3d612cf78de09d5 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 21 Jun 2024 15:33:56 -0700 Subject: [PATCH 22/52] fixup --- .../CSharp/InlineRename/CSharpEditorInlineRenameService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 7458a8d083733..5ba64c17aed01 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; From 5abd3f1ec52beabbbbbbc601da99d00b28162ba8 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 24 Jun 2024 14:50:43 -0700 Subject: [PATCH 23/52] PR feedback --- .../CSharpEditorInlineRenameService.cs | 29 +++++++++++++------ .../UI/SmartRename/SmartRenameViewModel.cs | 1 - 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 5ba64c17aed01..f979aaa525273 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -26,15 +26,23 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { + /// + /// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature + /// + /// Instance of pertinent to the rename session. + /// of references discovered for the rename session. + /// Cancellation token + /// Map where key indicates the kind of semantic information, and value is an array of relevant code snippets. protected override async Task>> GetRenameContextCoreAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { - var seen = PooledHashSet.GetInstance(); - var definitions = ArrayBuilder.GetInstance(); - var references = ArrayBuilder.GetInstance(); - var docComments = ArrayBuilder.GetInstance(); + using var _1 = PooledHashSet.GetInstance(out var seen); + using var _2 = ArrayBuilder.GetInstance(out var definitions); + using var _3 = ArrayBuilder.GetInstance(out var references); + using var _4 = ArrayBuilder.GetInstance(out var docComments); foreach (var renameDefinition in inlineRenameInfo.DefinitionLocations) { + // Find largest snippet of code that represents the definition var containingStatementOrDeclarationSpan = await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false); @@ -45,6 +53,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( continue; } + // Find documentation comments using optional service var symbolService = renameDefinition.Document.GetLanguageService(); if (symbolService is not null) { @@ -64,6 +73,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( foreach (var renameLocation in inlineRenameLocationSet.Locations) { + // Find largest snippet of code that represents the reference var containingStatementOrDeclarationSpan = await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? @@ -78,20 +88,21 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(re AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references); } - var context = ImmutableDictionary>.Empty; + var contextBuilder = ImmutableDictionary.CreateBuilder>(); if (!definitions.IsEmpty) { - context = context.Add("definition", definitions.ToImmutableAndFree()); + contextBuilder.Add("definition", definitions.ToImmutableAndFree()); } if (!references.IsEmpty) { - context = context.Add("reference", references.ToImmutableAndFree()); + contextBuilder.Add("reference", references.ToImmutableAndFree()); } if (!docComments.IsEmpty) { - context = context.Add("documentation", docComments.ToImmutableAndFree()); + contextBuilder.Add("documentation", docComments.ToImmutableAndFree()); } - return context; + + return contextBuilder.ToImmutableDictionary(); void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder resultBuilder) { diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index c733699bfae03..fae1904ddbe60 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -164,7 +164,6 @@ private void OnGetSuggestionsCommandExecute() private async Task GetSuggestionsTaskAsync(CancellationToken cancellationToken) { var document = this.BaseViewModel.Session.TriggerDocument; - _ = document.GetLanguageService(); var editorRenameService = document.GetRequiredLanguageService(); var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) .ConfigureAwait(true); From 13e1afbedf71ef78e50cd264a5c7ee1fed9dd225 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 24 Jun 2024 15:09:11 -0700 Subject: [PATCH 24/52] Add IEditorInlineRenameService.IsRenameContextSupported --- .../UI/SmartRename/SmartRenameViewModel.cs | 18 +++++++++++------- .../VSTypeScriptEditorInlineRenameService.cs | 2 ++ .../AbstractEditorInlineRenameService.cs | 2 ++ .../InlineRename/IEditorInlineRenameService.cs | 2 ++ .../Editor/FSharpEditorInlineRenameService.cs | 2 ++ .../XamlEditorInlineRenameService.cs | 2 ++ 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index fae1904ddbe60..31aba4910592e 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -164,14 +164,18 @@ private void OnGetSuggestionsCommandExecute() private async Task GetSuggestionsTaskAsync(CancellationToken cancellationToken) { var document = this.BaseViewModel.Session.TriggerDocument; + var smartRenameContext = ImmutableDictionary.Empty; var editorRenameService = document.GetRequiredLanguageService(); - var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) - .ConfigureAwait(true); - var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) - .ConfigureAwait(true); - var smartRenameContext = ImmutableDictionary.CreateRange( - context - .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); + if (editorRenameService.IsRenameContextSupported) + { + var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) + .ConfigureAwait(true); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) + .ConfigureAwait(true); + smartRenameContext = ImmutableDictionary.CreateRange( + context + .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); + } _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken) .ConfigureAwait(true); diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index 4f88d81441d64..f434ed53f4177 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -24,6 +24,8 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; + public bool IsRenameContextSupported => false; + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 14b4bd1e510bc..502322fb814a0 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -32,6 +32,8 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } + public bool IsRenameContextSupported => true; + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return GetRenameContextCoreAsync(inlineRenameInfo, inlineRenameLocationSet, cancellationToken); diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index 6f320f649955d..6d220f2c8b4da 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -256,6 +256,8 @@ internal interface IEditorInlineRenameService : ILanguageService { Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); + bool IsRenameContextSupported { get; } + Task>> GetRenameContextAsync( IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index e6c8670ed7732..7a518e3c71720 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,6 +202,8 @@ public FSharpEditorInlineRenameService( _service = service; } + public bool IsRenameContextSupported => true; + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index 215018e9044bd..e480c110155cc 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,6 +30,8 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } + public bool IsRenameContextSupported => true; + public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); From c13c161662c10fbb34a728b3aaad38a19ab880ce Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 24 Jun 2024 15:09:19 -0700 Subject: [PATCH 25/52] revert unnecessary change --- .../InlineRename/UI/SmartRename/SmartRenameViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 31aba4910592e..fc0e27869794d 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -223,7 +223,7 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) // The previous element of first element is the last one. And the next element of the last element is the first one. var currentIndex = SuggestedNames.IndexOf(currentIdentifier); currentIndex += down ? 1 : -1; - var count = SuggestedNames.Count; + var count = this.SuggestedNames.Count; currentIndex = (currentIndex + count) % count; return SuggestedNames[currentIndex]; } From a342e0a4ed8f74b167237c11853b163db7e4c212 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 24 Jun 2024 15:11:29 -0700 Subject: [PATCH 26/52] remove unnecessary changes --- .../Core/InlineRename/IEditorInlineRenameService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index 6d220f2c8b4da..9048e1a15d66c 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -25,8 +25,8 @@ internal readonly struct InlineRenameLocation public InlineRenameLocation(Document document, TextSpan textSpan) : this() { - Document = document; - TextSpan = textSpan; + this.Document = document; + this.TextSpan = textSpan; } } @@ -73,9 +73,9 @@ internal readonly struct InlineRenameReplacement public InlineRenameReplacement(InlineRenameReplacementKind kind, TextSpan originalSpan, TextSpan newSpan) : this() { - Kind = kind; - OriginalSpan = originalSpan; - NewSpan = newSpan; + this.Kind = kind; + this.OriginalSpan = originalSpan; + this.NewSpan = newSpan; } internal InlineRenameReplacement(RelatedLocation location, TextSpan newSpan) From 1ff97d8523d18ac3bd07d459e1d4c4cbcd736a88 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 24 Jun 2024 15:14:41 -0700 Subject: [PATCH 27/52] doc comments --- .../Core/InlineRename/IEditorInlineRenameService.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index 9048e1a15d66c..168d9024539b0 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -254,10 +254,23 @@ internal interface IInlineRenameInfo /// internal interface IEditorInlineRenameService : ILanguageService { + /// + /// Returns necessary to establish the inline rename session. + /// Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); + /// + /// Returns whether this language service can return optional context from . + /// bool IsRenameContextSupported { get; } + /// + /// Returns optional context used in Copilot addition to inline rename feature. + /// + /// + /// + /// + /// Task>> GetRenameContextAsync( IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, From d0ef8d855fd22475b9a41b81aa829413784ce8b6 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 24 Jun 2024 15:19:53 -0700 Subject: [PATCH 28/52] expose TriggerDocument instead of creating a property that returns a field --- .../Core/InlineRename/InlineRenameSession.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 0f46eedebf25f..87285ffa00d59 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -49,14 +49,11 @@ internal partial class InlineRenameSession : IInlineRenameSession, IFeatureContr private readonly IEnumerable _refactorNotifyServices; private readonly IAsynchronousOperationListener _asyncListener; private readonly Solution _baseSolution; - private readonly Document _triggerDocument; private readonly ITextView _triggerView; private readonly IDisposable _inlineRenameSessionDurationLogBlock; private readonly IThreadingContext _threadingContext; public readonly InlineRenameService RenameService; - internal Document TriggerDocument => _triggerDocument; - private bool _dismissed; private bool _isApplyingEdit; private string _replacementText; @@ -64,6 +61,11 @@ internal partial class InlineRenameSession : IInlineRenameSession, IFeatureContr private bool _previewChanges; private readonly Dictionary _openTextBuffers = []; + /// + /// The original where rename was triggered + /// + public Document TriggerDocument { get; } + /// /// The original for the identifier that rename was triggered on /// @@ -153,8 +155,8 @@ public InlineRenameSession( _renameInfo = renameInfo; TriggerSpan = triggerSpan; - _triggerDocument = triggerSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (_triggerDocument == null) + TriggerDocument = triggerSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (TriggerDocument == null) { throw new InvalidOperationException(EditorFeaturesResources.The_triggerSpan_is_not_included_in_the_given_workspace); } @@ -185,7 +187,7 @@ public InlineRenameSession( _initialRenameText = triggerSpan.GetText(); this.ReplacementText = _initialRenameText; - _baseSolution = _triggerDocument.Project.Solution; + _baseSolution = TriggerDocument.Project.Solution; this.UndoManager = workspace.Services.GetService(); FileRenameInfo = _renameInfo.GetFileRenameInfo(); @@ -829,7 +831,7 @@ private async Task CommitCoreAsync(IUIThreadOperationContext operationContext, b _renameInfo.FullDisplayName, _renameInfo.Glyph, newSolution, - _triggerDocument.Project.Solution); + TriggerDocument.Project.Solution); if (newSolution == null) { From 810ef6ed87bf59f40241adbd54e172d773c6a9a0 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 11:54:07 -0700 Subject: [PATCH 29/52] add feature flag to control whether we're getting context --- .../UI/SmartRename/SmartRenameViewModel.cs | 36 ++++++++++++------- .../InlineRenameUIOptionsStorage.cs | 1 + .../Def/Options/VisualStudioOptionStorage.cs | 1 + .../Core/Def/PackageRegistration.pkgdef | 6 ++++ 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index c733699bfae03..3dadc40dab371 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -57,6 +57,7 @@ internal sealed partial class SmartRenameViewModel : INotifyPropertyChanged, IDi public bool StatusMessageVisibility => _smartRenameSession.StatusMessageVisibility; public bool IsUsingResultPanel { get; set; } public bool IsUsingDropdown { get; set; } + public bool IsUsingContext { get; } private string? _selectedSuggestedName; @@ -131,6 +132,7 @@ public SmartRenameViewModel( GetSuggestionsCommand = new DelegateCommand(OnGetSuggestionsCommandExecute, null, threadingContext.JoinableTaskFactory); var getSuggestionsAutomatically = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsAutomatically); + IsUsingContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); IsUsingResultPanel = getSuggestionsAutomatically; IsUsingDropdown = !IsUsingResultPanel; SetupTelemetry(); @@ -163,19 +165,27 @@ private void OnGetSuggestionsCommandExecute() private async Task GetSuggestionsTaskAsync(CancellationToken cancellationToken) { - var document = this.BaseViewModel.Session.TriggerDocument; - _ = document.GetLanguageService(); - var editorRenameService = document.GetRequiredLanguageService(); - var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) - .ConfigureAwait(true); - var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) - .ConfigureAwait(true); - var smartRenameContext = ImmutableDictionary.CreateRange( - context - .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); - - _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken) - .ConfigureAwait(true); + if (IsUsingContext) + { + var document = this.BaseViewModel.Session.TriggerDocument; + _ = document.GetLanguageService(); + var editorRenameService = document.GetRequiredLanguageService(); + var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) + .ConfigureAwait(true); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) + .ConfigureAwait(true); + var smartRenameContext = ImmutableDictionary.CreateRange( + context + .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); + + _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken) + .ConfigureAwait(true); + } + else + { + _ = await _smartRenameSession.GetSuggestionsAsync(cancellationToken) + .ConfigureAwait(true); + } } private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs index 4e49a010b802e..7ec8df35832b9 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs @@ -12,4 +12,5 @@ internal sealed class InlineRenameUIOptionsStorage public static readonly Option2 CollapseUI = new("dotnet_collapse_inline_rename_ui", defaultValue: false); public static readonly Option2 CollapseSuggestionsPanel = new("dotnet_collapse_suggestions_in_inline_rename_ui", defaultValue: false); public static readonly Option2 GetSuggestionsAutomatically = new("dotnet_rename_get_suggestions_automatically", defaultValue: false); + public static readonly Option2 GetSuggestionsContext = new("visual_studio_enable_copilot_rename_context", defaultValue: false); } diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 8841c4fa3071a..d6ab3229771b0 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -373,6 +373,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_report_invalid_json_patterns", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ReportInvalidJsonPatterns")}, {"visual_studio_enable_key_binding_reset", new FeatureFlagStorage("Roslyn.KeybindingResetEnabled")}, {"visual_studio_enable_semantic_search", new FeatureFlagStorage("Roslyn.SemanticSearchEnabled")}, + {"visual_studio_enable_copilot_rename_context", new FeatureFlagStorage("Roslyn.CopilotRenameGetContext")}, {"visual_studio_key_binding_needs_reset", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeedsReset")}, {"visual_studio_key_binding_reset_never_show_again", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeverShowAgain")}, {"visual_studio_resharper_key_binding_status", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "ReSharperStatus")}, diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 25767a4f03745..a96735b51a426 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -45,6 +45,12 @@ "Title"="Enable C# Semantic Search" "PreviewPaneChannels"="IntPreview,int.main" +[$RootKey$\FeatureFlags\Roslyn\CopilotRenameGetContext] +"Description"="Add context to C# Copilot Rename." +"Value"=dword:00000000 +"Title"="Add context to C# Copilot Rename" +"PreviewPaneChannels"="IntPreview,int.main" + // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag [$RootKey$\FeatureFlags\Lsp\PullDiagnostics] "Description"="Enables the LSP-powered diagnostics for managed .Net projects" From e6e6fec9a02ed6383a46f29d511ba05d91fd0d93 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 12:40:35 -0700 Subject: [PATCH 30/52] improve event handler name --- .../InlineRename/UI/SmartRename/SmartRenameViewModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 45ec89ae891c3..71ef6b9d6c47b 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -125,7 +125,7 @@ public SmartRenameViewModel( _smartRenameSession.PropertyChanged += SessionPropertyChanged; BaseViewModel = baseViewModel; - BaseViewModel.PropertyChanged += IdentifierTextPropertyChanged; + BaseViewModel.PropertyChanged += BaseViewModelPropertyChanged; BaseViewModel.IdentifierText = baseViewModel.IdentifierText; SetupTelemetry(); @@ -261,7 +261,7 @@ public void Dispose() { _isDisposed = true; _smartRenameSession.PropertyChanged -= SessionPropertyChanged; - BaseViewModel.PropertyChanged -= IdentifierTextPropertyChanged; + BaseViewModel.PropertyChanged -= BaseViewModelPropertyChanged; _smartRenameSession.Dispose(); _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); @@ -295,7 +295,7 @@ public void ToggleOrTriggerSuggestions() private void NotifyPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - private void IdentifierTextPropertyChanged(object sender, PropertyChangedEventArgs e) + private void BaseViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(BaseViewModel.IdentifierText)) { From 31770e1086ece3c1c78848f67821561c94110a58 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 13:23:21 -0700 Subject: [PATCH 31/52] fix Correctness_Analyzers warnings --- .../CSharp/InlineRename/CSharpRenameContextHelper.cs | 2 +- src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs index c7e5fac517a86..4dc7d92dbeac8 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs @@ -582,7 +582,7 @@ public static async Task> GetRelevantTypesAsync( var kind = token.Kind(); - bool isInterestingKind = + var isInterestingKind = kind is SyntaxKind.IdentifierToken || kind is SyntaxKind.BaseKeyword || kind is SyntaxKind.ThisKeyword; diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index 000b179a7b2c3..904d780ed3d64 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -442,7 +442,6 @@ public static Func CreateFunctionAccessor Date: Thu, 27 Jun 2024 13:23:32 -0700 Subject: [PATCH 32/52] update Feature Flag look --- src/VisualStudio/Core/Def/PackageRegistration.pkgdef | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index a96735b51a426..583c34e103d72 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -46,9 +46,9 @@ "PreviewPaneChannels"="IntPreview,int.main" [$RootKey$\FeatureFlags\Roslyn\CopilotRenameGetContext] -"Description"="Add context to C# Copilot Rename." +"Description"="Add semantic context to Copilot Rename Suggestions in C#." "Value"=dword:00000000 -"Title"="Add context to C# Copilot Rename" +"Title"="Semantic Context in C# Copilot Rename" "PreviewPaneChannels"="IntPreview,int.main" // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag From 1bc796ad1a2ad81fca3ecec01ab659df88257c9c Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 13:23:44 -0700 Subject: [PATCH 33/52] fix Freeing Twice assert --- .../CSharp/InlineRename/CSharpEditorInlineRenameService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index f979aaa525273..b052a56246f7b 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -91,15 +91,15 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(re var contextBuilder = ImmutableDictionary.CreateBuilder>(); if (!definitions.IsEmpty) { - contextBuilder.Add("definition", definitions.ToImmutableAndFree()); + contextBuilder.Add("definition", definitions.ToImmutable()); } if (!references.IsEmpty) { - contextBuilder.Add("reference", references.ToImmutableAndFree()); + contextBuilder.Add("reference", references.ToImmutable()); } if (!docComments.IsEmpty) { - contextBuilder.Add("documentation", docComments.ToImmutableAndFree()); + contextBuilder.Add("documentation", docComments.ToImmutable()); } return contextBuilder.ToImmutableDictionary(); From 064eb2e94658c22f9e46860689c8c3f950cbd751 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 13:57:48 -0700 Subject: [PATCH 34/52] add 'requires restart' to the FF name --- src/VisualStudio/Core/Def/PackageRegistration.pkgdef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 583c34e103d72..aaf0e6d907cd4 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -48,7 +48,7 @@ [$RootKey$\FeatureFlags\Roslyn\CopilotRenameGetContext] "Description"="Add semantic context to Copilot Rename Suggestions in C#." "Value"=dword:00000000 -"Title"="Semantic Context in C# Copilot Rename" +"Title"="Semantic Context in C# Copilot Rename (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag From 0c0c2ea7a96a1157a9c1dc003c486ec384262eae Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 15:53:18 -0700 Subject: [PATCH 35/52] cleanup usings --- .../InlineRename/UI/SmartRename/SmartRenameViewModel.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 71ef6b9d6c47b..919b8cc1dc72a 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -9,21 +9,17 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.Remoting.Contexts; using System.Threading; using System.Threading.Tasks; -using System.Windows.Input; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Editor.InlineRename; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.EditorFeatures.Lightup; -using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.PlatformUI; namespace Microsoft.CodeAnalysis.InlineRename.UI.SmartRename; From 121c396094b90beec7b7a7e59c99b86bffe7ce44 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 15:53:26 -0700 Subject: [PATCH 36/52] extract constants --- .../InlineRename/CSharpEditorInlineRenameService.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index b052a56246f7b..6318b948a241e 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -26,6 +26,9 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { + private const int ContextTargetLineCount = 10; + private const int ContextTargetHalfLineCount = 5; + /// /// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature /// @@ -117,11 +120,11 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? } // If a well defined surrounding span was not computed or if the computed surrounding span was too large, - // select a span that encompasses 5 lines above and 5 lines below the error squiggle. - if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > 10) + // select a span that encompasses ContextTargetHalfLineCount lines above and ContextTargetHalfLineCount lines below the error squiggle. + if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > ContextTargetLineCount) { - startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - 5); - endLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + 5); + startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - ContextTargetHalfLineCount); + endLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + ContextTargetHalfLineCount); } // If the start and end positions are not at the beginning and end of the start and end lines respectively, From f022adf6e3af2d0a118f399a4ac0780ae24dba7f Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Thu, 27 Jun 2024 16:59:41 -0700 Subject: [PATCH 37/52] remove unused code --- .../CSharp/InlineRename/CSharpRenameContextHelper.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs index 4dc7d92dbeac8..29a1063802368 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs @@ -385,12 +385,6 @@ portableExecutableReference.FilePath is { } portableExecutableFilePath && return null; } - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (model is null) - { - return null; - } - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (root is null) { From bbe2e4476d09b47f75074a30cf18efd34920f548 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Sun, 30 Jun 2024 20:17:49 -0700 Subject: [PATCH 38/52] NumberOfContextLines const --- .../InlineRename/CSharpEditorInlineRenameService.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 6318b948a241e..41457a3e3960a 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -26,8 +26,7 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { - private const int ContextTargetLineCount = 10; - private const int ContextTargetHalfLineCount = 5; + private const int NumberOfContextLines = 5; /// /// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature @@ -121,10 +120,10 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? // If a well defined surrounding span was not computed or if the computed surrounding span was too large, // select a span that encompasses ContextTargetHalfLineCount lines above and ContextTargetHalfLineCount lines below the error squiggle. - if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > ContextTargetLineCount) + if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > NumberOfContextLines * 2) { - startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - ContextTargetHalfLineCount); - endLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + ContextTargetHalfLineCount); + startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - NumberOfContextLines); + endLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + NumberOfContextLines); } // If the start and end positions are not at the beginning and end of the start and end lines respectively, From c30caf365e0b31c5342ee993b0e9df9c96c20034 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Sun, 30 Jun 2024 20:19:48 -0700 Subject: [PATCH 39/52] GetRenameContextCoreAsync doc comments --- .../CSharp/InlineRename/CSharpEditorInlineRenameService.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 41457a3e3960a..77f3e3a926a28 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -31,11 +31,9 @@ internal sealed class CSharpEditorInlineRenameService( /// /// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature /// - /// Instance of pertinent to the rename session. - /// of references discovered for the rename session. - /// Cancellation token /// Map where key indicates the kind of semantic information, and value is an array of relevant code snippets. - protected override async Task>> GetRenameContextCoreAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) + protected override async Task>> GetRenameContextCoreAsync( + IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { using var _1 = PooledHashSet.GetInstance(out var seen); using var _2 = ArrayBuilder.GetInstance(out var definitions); From bf35972fdfcdddd47c4cf1d84866a8987e41d20b Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Sun, 30 Jun 2024 20:24:20 -0700 Subject: [PATCH 40/52] pr feedback on CSharpEditorInlineRenameService --- .../CSharpEditorInlineRenameService.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 77f3e3a926a28..581ccdfcb36f9 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -47,14 +47,8 @@ protected override async Task await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false); - var documentText = await renameDefinition.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (documentText is null) - { - continue; - } - - // Find documentation comments using optional service - var symbolService = renameDefinition.Document.GetLanguageService(); + // Find documentation comments of definitions + var symbolService = renameDefinition.Document.GetRequiredLanguageService(); if (symbolService is not null) { var textSpan = inlineRenameInfo.TriggerSpan; @@ -68,6 +62,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( } } + var documentText = await renameDefinition.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } @@ -80,11 +75,6 @@ await renameLocation.Document.TryGetSurroundingNodeSpanAsync(re await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false); var documentText = await renameLocation.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (documentText is null) - { - continue; - } - AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references); } @@ -117,7 +107,7 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? } // If a well defined surrounding span was not computed or if the computed surrounding span was too large, - // select a span that encompasses ContextTargetHalfLineCount lines above and ContextTargetHalfLineCount lines below the error squiggle. + // select a span that encompasses NumberOfContextLines lines above and NumberOfContextLines lines below the identifier. if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > NumberOfContextLines * 2) { startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - NumberOfContextLines); From c8788ae681c985cdc2b370100cc763e555396675 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Sun, 30 Jun 2024 20:52:45 -0700 Subject: [PATCH 41/52] override fixes --- .../InlineRename/CSharpEditorInlineRenameService.cs | 4 +++- .../InlineRename/AbstractEditorInlineRenameService.cs | 9 ++------- .../Internal/Editor/FSharpEditorInlineRenameService.cs | 2 +- .../InlineRename/XamlEditorInlineRenameService.cs | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 581ccdfcb36f9..4ea254a3b2f55 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -28,11 +28,13 @@ internal sealed class CSharpEditorInlineRenameService( { private const int NumberOfContextLines = 5; + public override bool IsRenameContextSupported => true; + /// /// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature /// /// Map where key indicates the kind of semantic information, and value is an array of relevant code snippets. - protected override async Task>> GetRenameContextCoreAsync( + public override async Task>> GetRenameContextAsync( IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { using var _1 = PooledHashSet.GetInstance(out var seen); diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 502322fb814a0..5746cbcf87177 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -32,14 +32,9 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, _globalOptions.CreateProvider(), cancellationToken); } - public bool IsRenameContextSupported => true; + public virtual bool IsRenameContextSupported => false; - public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) - { - return GetRenameContextCoreAsync(inlineRenameInfo, inlineRenameLocationSet, cancellationToken); - } - - protected virtual Task>> GetRenameContextCoreAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) + public virtual Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); } diff --git a/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index 7a518e3c71720..f5a0f49897a9d 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,7 +202,7 @@ public FSharpEditorInlineRenameService( _service = service; } - public bool IsRenameContextSupported => true; + public bool IsRenameContextSupported => false; public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index e480c110155cc..7561454c1829b 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,7 +30,7 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } - public bool IsRenameContextSupported => true; + public bool IsRenameContextSupported => false; public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { From 028dd587a4148ba1ff4a81df609a7b69edae9cbf Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Sun, 30 Jun 2024 21:01:22 -0700 Subject: [PATCH 42/52] enforce hard limits on amount of symbols we traverse --- .../CSharp/InlineRename/CSharpEditorInlineRenameService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 4ea254a3b2f55..464dabf9ec9c8 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -27,6 +28,8 @@ internal sealed class CSharpEditorInlineRenameService( IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { private const int NumberOfContextLines = 5; + private const int MaxDefinitionCount = 10; + private const int MaxReferenceCount = 50; public override bool IsRenameContextSupported => true; @@ -42,7 +45,7 @@ public override async Task>> using var _3 = ArrayBuilder.GetInstance(out var references); using var _4 = ArrayBuilder.GetInstance(out var docComments); - foreach (var renameDefinition in inlineRenameInfo.DefinitionLocations) + foreach (var renameDefinition in inlineRenameInfo.DefinitionLocations.Take(MaxDefinitionCount)) { // Find largest snippet of code that represents the definition var containingStatementOrDeclarationSpan = @@ -68,7 +71,7 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( AddSpanOfInterest(documentText, renameDefinition.SourceSpan, containingStatementOrDeclarationSpan, definitions); } - foreach (var renameLocation in inlineRenameLocationSet.Locations) + foreach (var renameLocation in inlineRenameLocationSet.Locations.Take(MaxReferenceCount)) { // Find largest snippet of code that represents the reference var containingStatementOrDeclarationSpan = From 86909151c3f32139022165b82c4898236c97786b Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Sun, 30 Jun 2024 21:07:49 -0700 Subject: [PATCH 43/52] Get context off UI thread, respond to property changed on UI thread --- .../UI/SmartRename/SmartRenameViewModel.cs | 55 +++++++------- t | 74 +++++++++++++++++++ 2 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 t diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 919b8cc1dc72a..93292d50c8387 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -70,7 +70,7 @@ internal sealed partial class SmartRenameViewModel : INotifyPropertyChanged, IDi /// /// Determines whether smart rename gets semantic context to augment the request for suggested names. /// - public bool IsUsingContext { get; } + public bool IsUsingSemanticContext { get; } private string? _selectedSuggestedName; @@ -127,7 +127,7 @@ public SmartRenameViewModel( SetupTelemetry(); this.SupportsAutomaticSuggestions = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsAutomatically); - this.IsUsingContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); + this.IsUsingSemanticContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); // Use existing "CollapseSuggestionsPanel" option (true if user does not wish to get suggestions automatically) to honor user's choice. this.IsAutomaticSuggestionsEnabled = this.SupportsAutomaticSuggestions && !_globalOptionService.GetOption(InlineRenameUIOptionsStorage.CollapseSuggestionsPanel); if (this.IsAutomaticSuggestionsEnabled) @@ -159,9 +159,8 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can { if (isAutomaticOnInitialization) { - // ConfigureAwait(true) to stay on the UI thread; - // WPF view is bound to _smartRenameSession properties and so they must be updated on the UI thread. - await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken).ConfigureAwait(true); + await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken) + .ConfigureAwait(false); } if (cancellationToken.IsCancellationRequested || _isDisposed) @@ -169,7 +168,7 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can return; } - if (IsUsingContext) + if (IsUsingSemanticContext) { var document = this.BaseViewModel.Session.TriggerDocument; var smartRenameContext = ImmutableDictionary.Empty; @@ -177,48 +176,52 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can if (editorRenameService.IsRenameContextSupported) { var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) - .ConfigureAwait(true); + .ConfigureAwait(false); var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) - .ConfigureAwait(true); + .ConfigureAwait(false); smartRenameContext = ImmutableDictionary.CreateRange( context .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); } _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken) - .ConfigureAwait(true); + .ConfigureAwait(false); } else { _ = await _smartRenameSession.GetSuggestionsAsync(cancellationToken) - .ConfigureAwait(true); + .ConfigureAwait(false); } } private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) { - _threadingContext.ThrowIfNotOnUIThread(); - // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. - if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) + _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => { - var textInputBackup = BaseViewModel.IdentifierText; + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - SuggestedNames.Clear(); - // Set limit of 3 results - foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) + // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. + if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) { - SuggestedNames.Add(name); - } + var textInputBackup = BaseViewModel.IdentifierText; - // Changing the list may have changed the text in the text box. We need to restore it. - BaseViewModel.IdentifierText = textInputBackup; + SuggestedNames.Clear(); + // Set limit of 3 results + foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) + { + SuggestedNames.Add(name); + } - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); - return; - } + // Changing the list may have changed the text in the text box. We need to restore it. + BaseViewModel.IdentifierText = textInputBackup; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); + return; + } - // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber - PropertyChanged?.Invoke(this, e); + // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber + PropertyChanged?.Invoke(this, e); + }); } public string? ScrollSuggestions(string currentIdentifier, bool down) diff --git a/t b/t new file mode 100644 index 0000000000000..c8580651ee05e --- /dev/null +++ b/t @@ -0,0 +1,74 @@ +diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +index 919b8cc1dc7..93292d50c83 100644 +--- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs ++++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +@@ -73 +73 @@ internal sealed partial class SmartRenameViewModel : INotifyPropertyChanged, IDi +- public bool IsUsingContext { get; } ++ public bool IsUsingSemanticContext { get; } +@@ -130 +130 @@ public SmartRenameViewModel( +- this.IsUsingContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); ++ this.IsUsingSemanticContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); +@@ -162,3 +162,2 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can +- // ConfigureAwait(true) to stay on the UI thread; +- // WPF view is bound to _smartRenameSession properties and so they must be updated on the UI thread. +- await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken).ConfigureAwait(true); ++ await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken) ++ .ConfigureAwait(false); +@@ -172 +171 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can +- if (IsUsingContext) ++ if (IsUsingSemanticContext) +@@ -180 +179 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can +- .ConfigureAwait(true); ++ .ConfigureAwait(false); +@@ -182 +181 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can +- .ConfigureAwait(true); ++ .ConfigureAwait(false); +@@ -189 +188 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can +- .ConfigureAwait(true); ++ .ConfigureAwait(false); +@@ -194 +193 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can +- .ConfigureAwait(true); ++ .ConfigureAwait(false); +@@ -200,3 +199 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) +- _threadingContext.ThrowIfNotOnUIThread(); +- // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. +- if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) ++ _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => +@@ -204 +201 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) +- var textInputBackup = BaseViewModel.IdentifierText; ++ await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); +@@ -206,3 +203,2 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) +- SuggestedNames.Clear(); +- // Set limit of 3 results +- foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) ++ // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. ++ if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) +@@ -210 +206,14 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) +- SuggestedNames.Add(name); ++ var textInputBackup = BaseViewModel.IdentifierText; ++ ++ SuggestedNames.Clear(); ++ // Set limit of 3 results ++ foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) ++ { ++ SuggestedNames.Add(name); ++ } ++ ++ // Changing the list may have changed the text in the text box. We need to restore it. ++ BaseViewModel.IdentifierText = textInputBackup; ++ ++ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); ++ return; +@@ -213,9 +222,3 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) +- // Changing the list may have changed the text in the text box. We need to restore it. +- BaseViewModel.IdentifierText = textInputBackup; +- +- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); +- return; +- } +- +- // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber +- PropertyChanged?.Invoke(this, e); ++ // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber ++ PropertyChanged?.Invoke(this, e); ++ }); From 171b026dc7e90bfc96cca8ca3baec10eb5ba0510 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 2 Jul 2024 23:37:20 -0700 Subject: [PATCH 44/52] undo unwanted file --- t | 74 --------------------------------------------------------------- 1 file changed, 74 deletions(-) delete mode 100644 t diff --git a/t b/t deleted file mode 100644 index c8580651ee05e..0000000000000 --- a/t +++ /dev/null @@ -1,74 +0,0 @@ -diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs -index 919b8cc1dc7..93292d50c83 100644 ---- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs -+++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs -@@ -73 +73 @@ internal sealed partial class SmartRenameViewModel : INotifyPropertyChanged, IDi -- public bool IsUsingContext { get; } -+ public bool IsUsingSemanticContext { get; } -@@ -130 +130 @@ public SmartRenameViewModel( -- this.IsUsingContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); -+ this.IsUsingSemanticContext = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsContext); -@@ -162,3 +162,2 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can -- // ConfigureAwait(true) to stay on the UI thread; -- // WPF view is bound to _smartRenameSession properties and so they must be updated on the UI thread. -- await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken).ConfigureAwait(true); -+ await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken) -+ .ConfigureAwait(false); -@@ -172 +171 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can -- if (IsUsingContext) -+ if (IsUsingSemanticContext) -@@ -180 +179 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can -- .ConfigureAwait(true); -+ .ConfigureAwait(false); -@@ -182 +181 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can -- .ConfigureAwait(true); -+ .ConfigureAwait(false); -@@ -189 +188 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can -- .ConfigureAwait(true); -+ .ConfigureAwait(false); -@@ -194 +193 @@ private async Task GetSuggestionsTaskAsync(bool isAutomaticOnInitialization, Can -- .ConfigureAwait(true); -+ .ConfigureAwait(false); -@@ -200,3 +199 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) -- _threadingContext.ThrowIfNotOnUIThread(); -- // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. -- if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) -+ _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => -@@ -204 +201 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) -- var textInputBackup = BaseViewModel.IdentifierText; -+ await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); -@@ -206,3 +203,2 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) -- SuggestedNames.Clear(); -- // Set limit of 3 results -- foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) -+ // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. -+ if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) -@@ -210 +206,14 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) -- SuggestedNames.Add(name); -+ var textInputBackup = BaseViewModel.IdentifierText; -+ -+ SuggestedNames.Clear(); -+ // Set limit of 3 results -+ foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) -+ { -+ SuggestedNames.Add(name); -+ } -+ -+ // Changing the list may have changed the text in the text box. We need to restore it. -+ BaseViewModel.IdentifierText = textInputBackup; -+ -+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); -+ return; -@@ -213,9 +222,3 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) -- // Changing the list may have changed the text in the text box. We need to restore it. -- BaseViewModel.IdentifierText = textInputBackup; -- -- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); -- return; -- } -- -- // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber -- PropertyChanged?.Invoke(this, e); -+ // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber -+ PropertyChanged?.Invoke(this, e); -+ }); From f4bc19850120098a75c4fbbd18b7f524bfe8fa23 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 9 Jul 2024 14:33:16 -0700 Subject: [PATCH 45/52] Remove CSharpRenameContextHelper; improve the provided context --- .../CSharpEditorInlineRenameService.cs | 41 +- .../InlineRename/CSharpRenameContextHelper.cs | 630 ------------------ 2 files changed, 35 insertions(+), 636 deletions(-) delete mode 100644 src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 464dabf9ec9c8..68c32ef33645d 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.GoToDefinition; @@ -27,7 +28,7 @@ internal sealed class CSharpEditorInlineRenameService( [ImportMany] IEnumerable refactorNotifyServices, IGlobalOptionService globalOptions) : AbstractEditorInlineRenameService(refactorNotifyServices, globalOptions) { - private const int NumberOfContextLines = 5; + private const int NumberOfContextLines = 20; private const int MaxDefinitionCount = 10; private const int MaxReferenceCount = 50; @@ -49,8 +50,8 @@ public override async Task>> { // Find largest snippet of code that represents the definition var containingStatementOrDeclarationSpan = - await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? - await renameDefinition.Document.TryGetSurroundingNodeSpanAsync(renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false); + await TryGetSurroundingNodeSpanAsync(renameDefinition.Document, renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false) ?? + await TryGetSurroundingNodeSpanAsync(renameDefinition.Document, renameDefinition.SourceSpan, cancellationToken).ConfigureAwait(false); // Find documentation comments of definitions var symbolService = renameDefinition.Document.GetRequiredLanguageService(); @@ -75,9 +76,9 @@ await renameDefinition.Document.TryGetSurroundingNodeSpanAsync( { // Find largest snippet of code that represents the reference var containingStatementOrDeclarationSpan = - await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? - await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? - await renameLocation.Document.TryGetSurroundingNodeSpanAsync(renameLocation.TextSpan, cancellationToken).ConfigureAwait(false); + await TryGetSurroundingNodeSpanAsync(renameLocation.Document, renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? + await TryGetSurroundingNodeSpanAsync(renameLocation.Document, renameLocation.TextSpan, cancellationToken).ConfigureAwait(false) ?? + await TryGetSurroundingNodeSpanAsync(renameLocation.Document, renameLocation.TextSpan, cancellationToken).ConfigureAwait(false); var documentText = await renameLocation.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references); @@ -133,4 +134,32 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? } } } + + /// + /// Returns the of the nearest encompassing of type + /// of which the supplied is a part within the supplied + /// . + /// + public static async Task TryGetSurroundingNodeSpanAsync( + Document document, + TextSpan textSpan, + CancellationToken cancellationToken) + where T : CSharpSyntaxNode + { + if (document.Project.Language is not LanguageNames.CSharp) + { + return null; + } + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (root is null) + { + return null; + } + + var containingNode = root.FindNode(textSpan); + var targetNode = containingNode.FirstAncestorOrSelf() ?? containingNode; + + return targetNode.Span; + } } diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs deleted file mode 100644 index 29a1063802368..0000000000000 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpRenameContextHelper.cs +++ /dev/null @@ -1,630 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Copied from VisualStudio.Conversations repo src/Copilot.Vsix/QuickActions/CSharp/CSharpTypeSignatureHelper.cs -// with minor changes - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.InlineRename -{ - internal static class RenameContextHelper - { - private const string Space = " "; - private const string Indentation = " "; - private const string OpeningBrace = "{"; - private const string ClosingBrace = "}"; - private const string Semicolon = ";"; - private const string ColonSeparator = " : "; - private const string CommaSeparator = ", "; - private const string Unknown = "?"; - - private const SymbolDisplayGenericsOptions GenericsOptions = - SymbolDisplayGenericsOptions.IncludeVariance | - SymbolDisplayGenericsOptions.IncludeTypeParameters; - - private const SymbolDisplayMiscellaneousOptions MiscellaneousOptions = - SymbolDisplayMiscellaneousOptions.UseSpecialTypes | - SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral | - SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | - SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix | - SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName; - - private static readonly SymbolDisplayFormat FormatForTypeDefinitions = - new(typeQualificationStyle: - SymbolDisplayTypeQualificationStyle.NameOnly, - genericsOptions: - GenericsOptions | - SymbolDisplayGenericsOptions.IncludeTypeConstraints, - delegateStyle: - SymbolDisplayDelegateStyle.NameAndSignature, - kindOptions: - SymbolDisplayKindOptions.IncludeTypeKeyword, - miscellaneousOptions: - MiscellaneousOptions); - - private static readonly SymbolDisplayFormat FormatForBaseTypeAndInterfacesInTypeDefinition = - new(typeQualificationStyle: - SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, - genericsOptions: - GenericsOptions, - miscellaneousOptions: - MiscellaneousOptions); - - private static readonly SymbolDisplayFormat FormatForMemberDefinitions = - new(typeQualificationStyle: - SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, - genericsOptions: - GenericsOptions | - SymbolDisplayGenericsOptions.IncludeTypeConstraints, - memberOptions: - SymbolDisplayMemberOptions.IncludeAccessibility | - SymbolDisplayMemberOptions.IncludeModifiers | - SymbolDisplayMemberOptions.IncludeRef | - SymbolDisplayMemberOptions.IncludeType | - SymbolDisplayMemberOptions.IncludeExplicitInterface | - SymbolDisplayMemberOptions.IncludeParameters | - SymbolDisplayMemberOptions.IncludeConstantValue, - extensionMethodStyle: - SymbolDisplayExtensionMethodStyle.StaticMethod, - parameterOptions: - SymbolDisplayParameterOptions.IncludeModifiers | - SymbolDisplayParameterOptions.IncludeExtensionThis | - SymbolDisplayParameterOptions.IncludeType | - SymbolDisplayParameterOptions.IncludeName | - SymbolDisplayParameterOptions.IncludeOptionalBrackets | - SymbolDisplayParameterOptions.IncludeDefaultValue, - propertyStyle: - SymbolDisplayPropertyStyle.ShowReadWriteDescriptor, - kindOptions: - SymbolDisplayKindOptions.IncludeMemberKeyword, - miscellaneousOptions: - MiscellaneousOptions | - SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); - - private static StringBuilder AppendKeyword(this StringBuilder builder, SyntaxKind keywordKind) - => builder.Append(SyntaxFacts.GetText(keywordKind)); - - // SymbolDisplay does not support displaying accessibility for types at the moment. - // See https://github.com/dotnet/roslyn/issues/28297. - private static void AppendAccessibility(this StringBuilder builder, ITypeSymbol type) - { - switch (type.DeclaredAccessibility) - { - case Accessibility.Private: - builder.AppendKeyword(SyntaxKind.PrivateKeyword); - break; - - case Accessibility.Internal: - builder.AppendKeyword(SyntaxKind.InternalKeyword); - break; - - case Accessibility.ProtectedAndInternal: - builder.AppendKeyword(SyntaxKind.PrivateKeyword); - builder.Append(Space); - builder.AppendKeyword(SyntaxKind.ProtectedKeyword); - break; - - case Accessibility.Protected: - builder.AppendKeyword(SyntaxKind.ProtectedKeyword); - break; - - case Accessibility.ProtectedOrInternal: - builder.AppendKeyword(SyntaxKind.ProtectedKeyword); - builder.Append(Space); - builder.AppendKeyword(SyntaxKind.InternalKeyword); - break; - - case Accessibility.Public: - builder.AppendKeyword(SyntaxKind.PublicKeyword); - break; - - default: - builder.Append(Unknown); - break; - } - - builder.Append(Space); - } - - // SymbolDisplay does not support displaying modifiers for types at the moment. - // See https://github.com/dotnet/roslyn/issues/28297. - private static void AppendModifiers(this StringBuilder builder, ITypeSymbol type) - { - if (type.TypeKind is TypeKind.Class && type.IsAbstract) - { - builder.AppendKeyword(SyntaxKind.AbstractKeyword); - builder.Append(Space); - } - - if (type.TypeKind is TypeKind.Class && type.IsSealed) - { - builder.AppendKeyword(SyntaxKind.SealedKeyword); - builder.Append(Space); - } - - if (type.IsStatic) - { - builder.AppendKeyword(SyntaxKind.StaticKeyword); - builder.Append(Space); - } - } - - private static void AppendBaseTypeAndInterfaces( - this StringBuilder builder, - ITypeSymbol type, - CancellationToken cancellationToken) - { - var baseType = type.BaseType; - - var baseTypeAndInterfaces = - baseType is null || - baseType.SpecialType is SpecialType.System_Object || - baseType.SpecialType is SpecialType.System_ValueType - ? type.AllInterfaces - : [baseType, .. type.AllInterfaces]; - - if (baseTypeAndInterfaces.IsDefaultOrEmpty) - { - return; - } - - builder.Append(ColonSeparator); - - for (var i = 0; i < baseTypeAndInterfaces.Length; ++i) - { - cancellationToken.ThrowIfCancellationRequested(); - - var baseTypeOrInterface = baseTypeAndInterfaces[i]; - - builder.Append( - Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(baseTypeOrInterface, FormatForBaseTypeAndInterfacesInTypeDefinition)); - - if (i < baseTypeAndInterfaces.Length - 1) - { - builder.Append(CommaSeparator); - } - } - } - - private static void AppendTypeDefinition( - this StringBuilder builder, - ITypeSymbol type, - CancellationToken cancellationToken) - { - builder.AppendAccessibility(type); - builder.AppendModifiers(type); - builder.Append(Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(type, FormatForTypeDefinitions)); - - if (type.TypeKind is TypeKind.Delegate) - { - builder.Append(Semicolon); - } - else - { - builder.AppendBaseTypeAndInterfaces(type, cancellationToken); - } - } - - private static void AppendMemberDefinition(this StringBuilder builder, ISymbol member) - { - Assumes.True( - member is IEventSymbol || - member is IFieldSymbol || - member is IPropertySymbol || - member is IMethodSymbol); - - var line = Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(member, FormatForMemberDefinitions); - builder.Append(line); - - if (!line.EndsWith(ClosingBrace)) - { - builder.Append(Semicolon); - } - } - - private static string GetIndentation(int level) - { - if (level is 0) - { - return string.Empty; - } - - if (level is 1) - { - return Indentation; - } - - var builder = PooledStringBuilder.GetInstance(); - - for (var i = 0; i < level; ++i) - { - builder.Builder.Append(Indentation); - } - - return builder.ToStringAndFree(); - } - - /// - /// Returns the type signature for the type specified by the supplied . - /// - /// - /// Signatures for all contained members are also included within a type's signature but method bodies are not. - /// - public static string GetSignature( - this ITypeSymbol type, - int indentLevel, - CancellationToken cancellationToken) - { - var builder = PooledStringBuilder.GetInstance(); - var indentation = GetIndentation(indentLevel); - var memberIndentation = GetIndentation(++indentLevel); - - builder.Builder.Append(indentation); - builder.Builder.AppendTypeDefinition(type, cancellationToken); - - if (type.TypeKind is not TypeKind.Delegate) - { - builder.Builder.AppendLine(); - builder.Builder.Append(indentation); - builder.Builder.AppendLine(OpeningBrace); - } - - foreach (var member in type.GetMembers()) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (member.CanBeReferencedByName && - !member.IsImplicitlyDeclared && - !member.HasUnsupportedMetadata) - { - if (member is IEventSymbol || - member is IFieldSymbol || - member is IPropertySymbol || - member is IMethodSymbol) - { - builder.Builder.Append(memberIndentation); - builder.Builder.AppendMemberDefinition(member); - builder.Builder.AppendLine(); - } - else if (member is ITypeSymbol nestedType) - { - var nestedTypeSignature = nestedType.GetSignature(indentLevel, cancellationToken); - builder.Builder.AppendLine(nestedTypeSignature); - } - } - } - - if (type.TypeKind is not TypeKind.Delegate) - { - builder.Builder.Append(indentation); - builder.Builder.Append(ClosingBrace); - } - - return builder.ToStringAndFree(); - } - - /// - /// Returns the file path(s) that contains the definition of the type specified by the supplied - /// . - /// - /// - /// A type's definition may be split across multiple files if the type is a type. - /// - public static async Task> GetDeclarationFilePathsAsync( - this ITypeSymbol type, - Document referencingDocument, - CancellationToken cancellationToken) - { - if (type.Locations.Length is 0) - { - return []; - } - - var builder = PooledHashSet.GetInstance(); - - foreach (var location in type.Locations) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (location.IsInSource && - location.SourceTree.FilePath is { } sourceFilePath && - !string.IsNullOrWhiteSpace(sourceFilePath)) - { - builder.Add(sourceFilePath); - } - else if (location.IsInMetadata && location.MetadataModule?.ContainingAssembly is { } assembly) - { - var referencingProject = referencingDocument.Project; - var compilation = await referencingProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var metadataReference = compilation?.GetMetadataReference(assembly); - - if (metadataReference is PortableExecutableReference portableExecutableReference && - portableExecutableReference.FilePath is { } portableExecutableFilePath && - !string.IsNullOrWhiteSpace(portableExecutableFilePath)) - { - builder.Add(portableExecutableFilePath); - } - else if (metadataReference?.Display is { } filePath && !string.IsNullOrWhiteSpace(filePath)) - { - builder.Add(filePath); - } - } - } - - var filePaths = builder.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); - builder.Free(); - - return filePaths; - } - - /// - /// Returns the of the nearest encompassing of type - /// of which the supplied is a part within the supplied - /// . - /// - public static async Task TryGetSurroundingNodeSpanAsync( - this Document document, - TextSpan span, - CancellationToken cancellationToken) - where T : CSharpSyntaxNode - { - if (document.Project.Language is not LanguageNames.CSharp) - { - return null; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - if (root is null) - { - return null; - } - - var containingNode = root.FindNode(span); - var targetNode = containingNode.FirstAncestorOrSelf() ?? containingNode; - - return targetNode.Span; - } - - private static string GetOutermostNamespaceName(this ITypeSymbol type, CancellationToken cancellationToken) - { - var outermostNamespaceName = string.Empty; - var currentNamespace = type.ContainingNamespace; - - while (currentNamespace is not null && !currentNamespace.IsGlobalNamespace) - { - cancellationToken.ThrowIfCancellationRequested(); - - outermostNamespaceName = currentNamespace.Name; - currentNamespace = currentNamespace.ContainingNamespace; - } - - return outermostNamespaceName; - } - - private static bool IsWellKnownType(this ITypeSymbol type, CancellationToken cancellationToken) - => type.GetOutermostNamespaceName(cancellationToken) is "System"; - - private static ImmutableArray RemoveDuplicatesAndPreserveOrder(this ImmutableArray spans) - { - if (spans.IsDefaultOrEmpty || spans.Length is 1) - { - return spans; - } - - if (spans.Length is 2) - { - return spans[0] == spans[1] ? [spans[0]] : spans; - } - - var seen = PooledHashSet.GetInstance(); - var builder = ArrayBuilder.GetInstance(); - - foreach (var span in spans) - { - if (seen.Add(span)) - { - builder.Add(span); - } - } - - seen.Free(); - return builder.ToImmutableAndFree(); - } - - private static bool AddType( - this ArrayBuilder relevantTypes, - ITypeSymbol type, - HashSet seenTypes, - CancellationToken cancellationToken) - { - if (type.TypeKind is TypeKind.Error || !seenTypes.Add(type)) - { - return false; - } - - if (type.ContainingType is { } containingType) - { - return relevantTypes.AddType(containingType, seenTypes, cancellationToken); - } - - if (type is IArrayTypeSymbol arrayType) - { - return relevantTypes.AddType(arrayType.ElementType, seenTypes, cancellationToken); - } - - if (type is INamedTypeSymbol namedType && namedType.IsGenericType) - { - foreach (var typeArgument in namedType.TypeArguments) - { - cancellationToken.ThrowIfCancellationRequested(); - - relevantTypes.AddType(typeArgument, seenTypes, cancellationToken); - } - - if (!namedType.Equals(namedType.ConstructedFrom, SymbolEqualityComparer.Default)) - { - return relevantTypes.AddType(namedType.ConstructedFrom, seenTypes, cancellationToken); - } - } - - if (type.TypeKind is TypeKind.Pointer || - type.TypeKind is TypeKind.TypeParameter || - type.TypeKind is TypeKind.Module || - type.TypeKind is TypeKind.Unknown || - type.SpecialType is not SpecialType.None || - type.IsWellKnownType(cancellationToken)) - { - return false; - } - - relevantTypes.Add(type); - return true; - } - - private static void AddTypeAlongWithBaseTypesAndInterfaces( - this ArrayBuilder relevantTypes, - ITypeSymbol type, - HashSet seenTypes, - CancellationToken cancellationToken) - { - if (!relevantTypes.AddType(type, seenTypes, cancellationToken)) - { - return; - } - - if (type.BaseType is { } baseType) - { - relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces(baseType, seenTypes, cancellationToken); - } - - foreach (var @interface in type.Interfaces) - { - cancellationToken.ThrowIfCancellationRequested(); - - relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces(@interface, seenTypes, cancellationToken); - } - } - - /// - /// Retrieves the s for the types that are referenced within the specified set of spans - /// within a document. - /// - /// - /// - /// Also returns s for related types such as base types and interfaces implemented by the - /// types that are directly referenced in the supplied set of spans. - /// - /// - /// The relative order in which the type references are encountered in the specified set of spans is preserved in - /// the returned collection of s. - /// - /// - public static async Task> GetRelevantTypesAsync( - this Document document, - ImmutableArray spansOfInterest, - CancellationToken cancellationToken) - { - if (document.Project.Language is not LanguageNames.CSharp) - { - return []; - } - - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (model is null) - { - return []; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - if (root is null) - { - return []; - } - - var relevantTypes = ArrayBuilder.GetInstance(); - var seenTypes = new HashSet(SymbolEqualityComparer.Default); - var seenSpans = PooledHashSet.GetInstance(); - - spansOfInterest = spansOfInterest.RemoveDuplicatesAndPreserveOrder(); - foreach (var spanOfInterest in spansOfInterest) - { - cancellationToken.ThrowIfCancellationRequested(); - - var containingNode = root.FindNode(spanOfInterest); - - var tokensOfInterest = - containingNode - .DescendantTokens() - .SkipWhile(t => t.Span.End < spanOfInterest.Start) - .TakeWhile(t => t.Span.Start <= spanOfInterest.End); - - foreach (var token in tokensOfInterest) - { - cancellationToken.ThrowIfCancellationRequested(); - - var kind = token.Kind(); - - var isInterestingKind = - kind is SyntaxKind.IdentifierToken || - kind is SyntaxKind.BaseKeyword || - kind is SyntaxKind.ThisKeyword; - - if (isInterestingKind && - spanOfInterest.Contains(token.Span) && - token.Parent is { } node && - seenSpans.Add(token.FullSpan) && - (node.FullSpan == token.FullSpan || seenSpans.Add(node.FullSpan))) - { - if (node is BaseTypeDeclarationSyntax typeSyntax && - model.GetDeclaredSymbol(typeSyntax, cancellationToken) is INamedTypeSymbol declaredType) - { - relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( - declaredType, - seenTypes, - cancellationToken); - } - else if (node is MemberDeclarationSyntax memberSyntax && - memberSyntax.FirstAncestorOrSelf() is { } containingTypeSyntax && - seenSpans.Add(containingTypeSyntax.FullSpan) && - model.GetDeclaredSymbol(containingTypeSyntax, cancellationToken) is INamedTypeSymbol containingDeclaredType) - { - relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( - containingDeclaredType, - seenTypes, - cancellationToken); - } - else if (model.GetTypeInfo(node, cancellationToken).Type is ITypeSymbol typeInfoType) - { - relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( - typeInfoType, - seenTypes, - cancellationToken); - } - else if (model.GetSymbolInfo(node, cancellationToken).Symbol is ITypeSymbol symbolInfoType) - { - relevantTypes.AddTypeAlongWithBaseTypesAndInterfaces( - symbolInfoType, - seenTypes, - cancellationToken); - } - } - } - } - - seenSpans.Free(); - return relevantTypes.ToImmutableAndFree(); - } - } -} From f3c9467eea955a39be7f8d9afe079575d081c358 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 9 Jul 2024 15:08:11 -0700 Subject: [PATCH 46/52] modifiers and doc comments --- .../Core/InlineRename/InlineRenameSession.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index 87285ffa00d59..2e44682ac8a6a 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -94,6 +94,17 @@ private set /// public InlineRenameFileRenameInfo FileRenameInfo { get; } + /// + /// Information about this rename session. + /// + public IInlineRenameInfo RenameInfo => _renameInfo; + + /// + /// The task which computes the main rename locations against the original workspace + /// snapshot. + /// + public JoinableTask AllRenameLocationsTask => _allRenameLocationsTask; + /// /// Keep-alive session held alive with the OOP server. This allows us to pin the initial solution snapshot over on /// the oop side, which is valuable for preventing it from constantly being dropped/synced on every conflict @@ -126,9 +137,6 @@ private set private readonly IInlineRenameInfo _renameInfo; - internal IInlineRenameInfo RenameInfo => _renameInfo; - internal JoinableTask AllRenameLocationsTask => _allRenameLocationsTask; - /// /// The initial text being renamed. /// From 6c06fa3a3684f7639312255828a155d361b7fb68 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Tue, 9 Jul 2024 15:11:47 -0700 Subject: [PATCH 47/52] remove never taken branch --- .../CSharp/InlineRename/CSharpEditorInlineRenameService.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 68c32ef33645d..312cdd2226894 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -146,11 +146,6 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? CancellationToken cancellationToken) where T : CSharpSyntaxNode { - if (document.Project.Language is not LanguageNames.CSharp) - { - return null; - } - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (root is null) { From 1faf7170877585c24700037f0a2aaa021161fe00 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Wed, 10 Jul 2024 10:26:18 -0700 Subject: [PATCH 48/52] Remove IsRenameContextSupported --- .../CSharpEditorInlineRenameService.cs | 2 -- .../UI/SmartRename/SmartRenameViewModel.cs | 18 +++++++----------- .../VSTypeScriptEditorInlineRenameService.cs | 2 -- .../AbstractEditorInlineRenameService.cs | 2 -- .../InlineRename/IEditorInlineRenameService.cs | 5 ----- .../Editor/FSharpEditorInlineRenameService.cs | 2 -- .../XamlEditorInlineRenameService.cs | 2 -- 7 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index a357767df40da..2150cb28863df 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -31,8 +31,6 @@ internal sealed class CSharpEditorInlineRenameService([ImportMany] IEnumerable true; - /// /// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature /// diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 93292d50c8387..1930e58a2c594 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -173,17 +173,13 @@ await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken) var document = this.BaseViewModel.Session.TriggerDocument; var smartRenameContext = ImmutableDictionary.Empty; var editorRenameService = document.GetRequiredLanguageService(); - if (editorRenameService.IsRenameContextSupported) - { - var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) - .ConfigureAwait(false); - var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) - .ConfigureAwait(false); - smartRenameContext = ImmutableDictionary.CreateRange( - context - .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); - } - + var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken) + .ConfigureAwait(false); + var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken) + .ConfigureAwait(false); + smartRenameContext = ImmutableDictionary.CreateRange( + context + .Select(n => new KeyValuePair(n.Key, n.Value.ToArray()))); _ = await _smartRenameSession.GetSuggestionsAsync(smartRenameContext, cancellationToken) .ConfigureAwait(false); } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs index f434ed53f4177..4f88d81441d64 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptEditorInlineRenameService.cs @@ -24,8 +24,6 @@ internal sealed class VSTypeScriptEditorInlineRenameService( { private readonly Lazy? _service = service; - public bool IsRenameContextSupported => false; - public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 0a199dea92816..0e2d7499747b6 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -29,8 +29,6 @@ public async Task GetRenameInfoAsync(Document document, int p _refactorNotifyServices, symbolicInfo, cancellationToken); } - public virtual bool IsRenameContextSupported => false; - public virtual Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); diff --git a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs index 168d9024539b0..ad4d3583fafd5 100644 --- a/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/IEditorInlineRenameService.cs @@ -259,11 +259,6 @@ internal interface IEditorInlineRenameService : ILanguageService /// Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken); - /// - /// Returns whether this language service can return optional context from . - /// - bool IsRenameContextSupported { get; } - /// /// Returns optional context used in Copilot addition to inline rename feature. /// diff --git a/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs b/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs index f5a0f49897a9d..e6c8670ed7732 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs +++ b/src/VisualStudio/ExternalAccess/FSharp/Internal/Editor/FSharpEditorInlineRenameService.cs @@ -202,8 +202,6 @@ public FSharpEditorInlineRenameService( _service = service; } - public bool IsRenameContextSupported => false; - public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); diff --git a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs index 7561454c1829b..215018e9044bd 100644 --- a/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/InlineRename/XamlEditorInlineRenameService.cs @@ -30,8 +30,6 @@ public XamlEditorInlineRenameService(IXamlRenameInfoService renameService) _renameService = renameService; } - public bool IsRenameContextSupported => false; - public Task>> GetRenameContextAsync(IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken) { return Task.FromResult(ImmutableDictionary>.Empty); From c3f3030cfff1ffe82cdacbdb7422814a5a9b5349 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 12 Jul 2024 13:37:32 -0700 Subject: [PATCH 49/52] PR feedback to move TryGetSurroundingNodeSpanAsync to parent class --- .../CSharpEditorInlineRenameService.cs | 23 ------------------ .../AbstractEditorInlineRenameService.cs | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index 2150cb28863df..eefb62a2c31d8 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -131,27 +131,4 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? } } } - - /// - /// Returns the of the nearest encompassing of type - /// of which the supplied is a part within the supplied - /// . - /// - public static async Task TryGetSurroundingNodeSpanAsync( - Document document, - TextSpan textSpan, - CancellationToken cancellationToken) - where T : CSharpSyntaxNode - { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - if (root is null) - { - return null; - } - - var containingNode = root.FindNode(textSpan); - var targetNode = containingNode.FirstAncestorOrSelf() ?? containingNode; - - return targetNode.Span; - } } diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs index 0e2d7499747b6..953187dae7267 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; @@ -33,4 +34,27 @@ public virtual Task>> GetRena { return Task.FromResult(ImmutableDictionary>.Empty); } + + /// + /// Returns the of the nearest encompassing of type + /// of which the supplied is a part within the supplied + /// . + /// + protected static async Task TryGetSurroundingNodeSpanAsync( + Document document, + TextSpan textSpan, + CancellationToken cancellationToken) + where T : SyntaxNode + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (root is null) + { + return null; + } + + var containingNode = root.FindNode(textSpan); + var targetNode = containingNode.FirstAncestorOrSelf() ?? containingNode; + + return targetNode.Span; + } } From f14318ad6cc583465b7fb392c8ae4b1cbf64207d Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 15 Jul 2024 13:38:52 -0700 Subject: [PATCH 50/52] use CompletesAsyncOperation pattern for SessionPropertyChanged --- .../UI/SmartRename/SmartRenameViewModel.cs | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 1930e58a2c594..7e19a74a59915 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -192,32 +192,36 @@ await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken) private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) { - _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => + var listener = _listenerProvider.GetListener(FeatureAttribute.SmartRename); + var listenerToken = listener.BeginAsyncOperation(nameof(SessionPropertyChanged)); + var sessionPropertyChangedTask = SessionPropertyChangedAsync(sender, e).CompletesAsyncOperation(listenerToken); + } + + private async Task SessionPropertyChangedAsync(object sender, PropertyChangedEventArgs e) + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. + if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + var textInputBackup = BaseViewModel.IdentifierText; - // _smartRenameSession.SuggestedNames is a normal list. We need to convert it to ObservableCollection to bind to UI Element. - if (e.PropertyName == nameof(_smartRenameSession.SuggestedNames)) + SuggestedNames.Clear(); + // Set limit of 3 results + foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) { - var textInputBackup = BaseViewModel.IdentifierText; - - SuggestedNames.Clear(); - // Set limit of 3 results - foreach (var name in _smartRenameSession.SuggestedNames.Take(3)) - { - SuggestedNames.Add(name); - } + SuggestedNames.Add(name); + } - // Changing the list may have changed the text in the text box. We need to restore it. - BaseViewModel.IdentifierText = textInputBackup; + // Changing the list may have changed the text in the text box. We need to restore it. + BaseViewModel.IdentifierText = textInputBackup; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); - return; - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuggestionsPanelExpanded))); + return; + } - // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber - PropertyChanged?.Invoke(this, e); - }); + // For the rest of the property, like HasSuggestions, IsAvailable and etc. Just forward it has changed to subscriber + PropertyChanged?.Invoke(this, e); } public string? ScrollSuggestions(string currentIdentifier, bool down) From 3a33f766e467cf4d6bd8c6610bb1cbc5092436d6 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 15 Jul 2024 22:22:10 -0700 Subject: [PATCH 51/52] Create IAsynchronousOperationListener in the constructor --- .../UI/SmartRename/SmartRenameViewModel.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 7e19a74a59915..8b680fae48f6f 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -31,7 +31,7 @@ internal sealed partial class SmartRenameViewModel : INotifyPropertyChanged, IDi private readonly IGlobalOptionService _globalOptionService; private readonly IThreadingContext _threadingContext; - private readonly IAsynchronousOperationListenerProvider _listenerProvider; + private readonly IAsynchronousOperationListener _asyncListener; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed; private TimeSpan AutomaticFetchDelay => _smartRenameSession.AutomaticFetchDelay; @@ -116,7 +116,7 @@ public SmartRenameViewModel( { _globalOptionService = globalOptionService; _threadingContext = threadingContext; - _listenerProvider = listenerProvider; + _asyncListener = listenerProvider.GetListener(FeatureAttribute.SmartRename); _smartRenameSession = smartRenameSession; _smartRenameSession.PropertyChanged += SessionPropertyChanged; @@ -147,8 +147,7 @@ private void FetchSuggestions(bool isAutomaticOnInitialization) if (_getSuggestionsTask.Status is TaskStatus.RanToCompletion or TaskStatus.Faulted or TaskStatus.Canceled) { - var listener = _listenerProvider.GetListener(FeatureAttribute.SmartRename); - var listenerToken = listener.BeginAsyncOperation(nameof(_smartRenameSession.GetSuggestionsAsync)); + var listenerToken = _asyncListener.BeginAsyncOperation(nameof(_smartRenameSession.GetSuggestionsAsync)); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new CancellationTokenSource(); _getSuggestionsTask = GetSuggestionsTaskAsync(isAutomaticOnInitialization, _cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); @@ -192,8 +191,7 @@ await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken) private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) { - var listener = _listenerProvider.GetListener(FeatureAttribute.SmartRename); - var listenerToken = listener.BeginAsyncOperation(nameof(SessionPropertyChanged)); + var listenerToken = _asyncListener.BeginAsyncOperation(nameof(SessionPropertyChanged)); var sessionPropertyChangedTask = SessionPropertyChangedAsync(sender, e).CompletesAsyncOperation(listenerToken); } From 3705723f29b64ab33436c637f11b48b5824ad214 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Mon, 15 Jul 2024 22:56:06 -0700 Subject: [PATCH 52/52] improve algorithm that provides context snippet --- .../CSharpEditorInlineRenameService.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs index eefb62a2c31d8..de443b34e138f 100644 --- a/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/CSharp/InlineRename/CSharpEditorInlineRenameService.cs @@ -107,11 +107,38 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? startLine = documentText.Lines.GetLineFromPosition(surroundingSpanOfInterest.Value.Start).LineNumber; endLine = documentText.Lines.GetLineFromPosition(surroundingSpanOfInterest.Value.End).LineNumber; lineCount = endLine - startLine + 1; + + if (lineCount > NumberOfContextLines * 2) + { + // The computed span is too large, trim it such that the fallback span is included + // and no content is provided from before startLine or after endLine. + var fallbackStartLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - NumberOfContextLines); + var fallbackEndLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + NumberOfContextLines); + var excessAtStart = startLine - fallbackStartLine; + var excessAtEnd = fallbackEndLine - endLine; + if (excessAtStart > 0) + { + // The fallback span extends before the relevant span (startLine) + endLine = Math.Min(documentText.Lines.Count - 1, fallbackEndLine + excessAtStart); + } + else if (excessAtEnd > 0) + { + // The fallback span extends after the relevant span (endLine) + startLine = Math.Max(0, fallbackStartLine - excessAtEnd); + } + else + { + // Fallback span surrounds the renamed identifier completely within startLine-endLine span. Use the fallback span as is. + startLine = fallbackStartLine; + endLine = fallbackEndLine; + } + lineCount = endLine - startLine + 1; + } } - // If a well defined surrounding span was not computed or if the computed surrounding span was too large, + // If a well defined surrounding span was not computed, // select a span that encompasses NumberOfContextLines lines above and NumberOfContextLines lines below the identifier. - if (surroundingSpanOfInterest is null || lineCount <= 0 || lineCount > NumberOfContextLines * 2) + if (surroundingSpanOfInterest is null || lineCount <= 0) { startLine = Math.Max(0, documentText.Lines.GetLineFromPosition(fallbackSpan.Start).LineNumber - NumberOfContextLines); endLine = Math.Min(documentText.Lines.Count - 1, documentText.Lines.GetLineFromPosition(fallbackSpan.End).LineNumber + NumberOfContextLines);