1919
2020namespace Microsoft . PowerShell . EditorServices . Services ;
2121
22+ /// <summary>
23+ /// Used with Configuration Bind to sync the settings to what is set on the client.
24+ /// </summary>
2225public class RenameServiceOptions
2326{
24- internal bool createFunctionAlias { get ; set ; }
25- internal bool createVariableAlias { get ; set ; }
26- internal bool acceptDisclaimer { get ; set ; }
27+ public bool createFunctionAlias { get ; set ; }
28+ public bool createVariableAlias { get ; set ; }
29+ public bool acceptDisclaimer { get ; set ; }
2730}
2831
2932public interface IRenameService
@@ -46,44 +49,44 @@ internal class RenameService(
4649 WorkspaceService workspaceService ,
4750 ILanguageServerFacade lsp ,
4851 ILanguageServerConfiguration config ,
52+ bool disclaimerDeclinedForSession = false ,
53+ bool disclaimerAcceptedForSession = false ,
4954 string configSection = "powershell.rename"
5055) : IRenameService
5156{
52- private bool disclaimerDeclined ;
53- private bool disclaimerAccepted ;
54-
55- private readonly RenameServiceOptions settings = new ( ) ;
5657
57- internal void RefreshSettings ( ) => config . GetSection ( configSection ) . Bind ( settings ) ;
58+ private async Task < RenameServiceOptions > GetScopedSettings ( DocumentUri uri , CancellationToken cancellationToken = default )
59+ {
60+ IScopedConfiguration scopedConfig = await config . GetScopedConfiguration ( uri , cancellationToken ) . ConfigureAwait ( false ) ;
61+ return scopedConfig . GetSection ( configSection ) . Get < RenameServiceOptions > ( ) ?? new RenameServiceOptions ( ) ;
62+ }
5863
5964 public async Task < RangeOrPlaceholderRange ? > PrepareRenameSymbol ( PrepareRenameParams request , CancellationToken cancellationToken )
6065 {
61-
62- if ( ! await AcceptRenameDisclaimer ( cancellationToken ) . ConfigureAwait ( false ) ) { return null ; }
63- ScriptFile scriptFile = workspaceService . GetFile ( request . TextDocument . Uri ) ;
64-
65- // TODO: Is this too aggressive? We can still rename inside a var/function even if dotsourcing is in use in a file, we just need to be clear it's not supported to expect rename actions to propogate.
66- if ( Utilities . AssertContainsDotSourced ( scriptFile . ScriptAst ) )
66+ RenameParams renameRequest = new ( )
6767 {
68- throw new HandlerErrorException ( "Dot Source detected, this is currently not supported" ) ;
69- }
70-
71- // TODO: FindRenamableSymbol may create false positives for renaming, so we probably should go ahead and execute a full rename and return true if edits are found.
72-
73- ScriptPositionAdapter position = request . Position ;
74- Ast ? target = FindRenamableSymbol ( scriptFile , position ) ;
68+ NewName = "PREPARERENAMETEST" , //A placeholder just to gather edits
69+ Position = request . Position ,
70+ TextDocument = request . TextDocument
71+ } ;
72+ // TODO: Should we cache these resuls and just fetch them on the actual rename, and move the bulk to an implementation method?
73+ WorkspaceEdit ? renameResponse = await RenameSymbol ( renameRequest , cancellationToken ) . ConfigureAwait ( false ) ;
7574
7675 // Since LSP 3.16 we can simply basically return a DefaultBehavior true or null to signal to the client that the position is valid for rename and it should use its default selection criteria (which is probably the language semantic highlighting or grammar). For the current scope of the rename provider, this should be fine, but we have the option to supply the specific range in the future for special cases.
77- RangeOrPlaceholderRange ? renamable = target is null ? null : new RangeOrPlaceholderRange
78- (
79- new RenameDefaultBehavior ( ) { DefaultBehavior = true }
80- ) ;
81- return renamable ;
76+ return ( renameResponse ? . Changes ? [ request . TextDocument . Uri ] . ToArray ( ) . Length > 0 )
77+ ? new RangeOrPlaceholderRange
78+ (
79+ new RenameDefaultBehavior ( ) { DefaultBehavior = true }
80+ )
81+ : null ;
8282 }
8383
8484 public async Task < WorkspaceEdit ? > RenameSymbol ( RenameParams request , CancellationToken cancellationToken )
8585 {
86- if ( ! await AcceptRenameDisclaimer ( cancellationToken ) . ConfigureAwait ( false ) ) { return null ; }
86+ // We want scoped settings because a workspace setting might be relevant here.
87+ RenameServiceOptions options = await GetScopedSettings ( request . TextDocument . Uri , cancellationToken ) . ConfigureAwait ( false ) ;
88+
89+ if ( ! await AcceptRenameDisclaimer ( options . acceptDisclaimer , cancellationToken ) . ConfigureAwait ( false ) ) { return null ; }
8790
8891 ScriptFile scriptFile = workspaceService . GetFile ( request . TextDocument . Uri ) ;
8992 ScriptPositionAdapter position = request . Position ;
@@ -95,7 +98,7 @@ internal class RenameService(
9598 TextEdit [ ] changes = tokenToRename switch
9699 {
97100 FunctionDefinitionAst or CommandAst => RenameFunction ( tokenToRename , scriptFile . ScriptAst , request ) ,
98- VariableExpressionAst => RenameVariable ( tokenToRename , scriptFile . ScriptAst , request ) ,
101+ VariableExpressionAst => RenameVariable ( tokenToRename , scriptFile . ScriptAst , request , options . createVariableAlias ) ,
99102 // FIXME: Only throw if capability is not prepareprovider
100103 _ => throw new InvalidOperationException ( "This should not happen as PrepareRename should have already checked for viability. File an issue if you see this." )
101104 } ;
@@ -122,17 +125,16 @@ internal static TextEdit[] RenameFunction(Ast target, Ast scriptAst, RenameParam
122125 return visitor . VisitAndGetEdits ( scriptAst ) ;
123126 }
124127
125- internal TextEdit [ ] RenameVariable ( Ast symbol , Ast scriptAst , RenameParams requestParams )
128+ internal static TextEdit [ ] RenameVariable ( Ast symbol , Ast scriptAst , RenameParams requestParams , bool createAlias )
126129 {
127130 if ( symbol is VariableExpressionAst or ParameterAst or CommandParameterAst or StringConstantExpressionAst )
128131 {
129-
130132 IterativeVariableRename visitor = new (
131133 requestParams . NewName ,
132134 symbol . Extent . StartLineNumber ,
133135 symbol . Extent . StartColumnNumber ,
134136 scriptAst ,
135- settings
137+ createAlias
136138 ) ;
137139 visitor . Visit ( scriptAst ) ;
138140 return visitor . Modifications . ToArray ( ) ;
@@ -200,15 +202,10 @@ internal static ScriptExtentAdapter GetFunctionNameExtent(FunctionDefinitionAst
200202 /// Prompts the user to accept the rename disclaimer.
201203 /// </summary>
202204 /// <returns>true if accepted, false if rejected</returns>
203- private async Task < bool > AcceptRenameDisclaimer ( CancellationToken cancellationToken )
205+ private async Task < bool > AcceptRenameDisclaimer ( bool acceptDisclaimerOption , CancellationToken cancellationToken )
204206 {
205- // Fetch the latest settings from the client, in case they have changed.
206- config . GetSection ( configSection ) . Bind ( settings ) ;
207-
208- // User has declined for the session so we don't want this popping up a bunch.
209- if ( disclaimerDeclined ) { return false ; }
210-
211- if ( settings . acceptDisclaimer || disclaimerAccepted ) { return true ; }
207+ if ( disclaimerDeclinedForSession ) { return false ; }
208+ if ( acceptDisclaimerOption || disclaimerAcceptedForSession ) { return true ; }
212209
213210 // TODO: Localization
214211 const string renameDisclaimer = "PowerShell rename functionality is only supported in a limited set of circumstances. Please review the notice and understand the limitations and risks." ;
@@ -242,8 +239,8 @@ private async Task<bool> AcceptRenameDisclaimer(CancellationToken cancellationTo
242239 Type = MessageType . Info
243240 } ;
244241 lsp . SendNotification ( msgParams ) ;
245- disclaimerDeclined = true ;
246- return ! disclaimerDeclined ;
242+ disclaimerDeclinedForSession = true ;
243+ return ! disclaimerDeclinedForSession ;
247244 }
248245 if ( result . Title == acceptAnswer )
249246 {
@@ -255,8 +252,8 @@ private async Task<bool> AcceptRenameDisclaimer(CancellationToken cancellationTo
255252 } ;
256253 lsp . SendNotification ( msgParams ) ;
257254
258- disclaimerAccepted = true ;
259- return disclaimerAccepted ;
255+ disclaimerAcceptedForSession = true ;
256+ return disclaimerAcceptedForSession ;
260257 }
261258 // if (result.Title == acceptWorkspaceAnswer)
262259 // {
@@ -514,7 +511,6 @@ public ScriptPositionAdapter(Position position) : this(position.Line + 1, positi
514511 scriptPosition . position . LineNumber - 1 , scriptPosition . position . ColumnNumber - 1
515512 ) ;
516513
517-
518514 public static implicit operator ScriptPositionAdapter ( ScriptPosition position ) => new ( position ) ;
519515 public static implicit operator ScriptPosition ( ScriptPositionAdapter position ) => position ;
520516
0 commit comments