19
19
20
20
namespace Microsoft . PowerShell . EditorServices . Services ;
21
21
22
+ /// <summary>
23
+ /// Used with Configuration Bind to sync the settings to what is set on the client.
24
+ /// </summary>
22
25
public class RenameServiceOptions
23
26
{
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 ; }
27
30
}
28
31
29
32
public interface IRenameService
@@ -46,44 +49,44 @@ internal class RenameService(
46
49
WorkspaceService workspaceService ,
47
50
ILanguageServerFacade lsp ,
48
51
ILanguageServerConfiguration config ,
52
+ bool disclaimerDeclinedForSession = false ,
53
+ bool disclaimerAcceptedForSession = false ,
49
54
string configSection = "powershell.rename"
50
55
) : IRenameService
51
56
{
52
- private bool disclaimerDeclined ;
53
- private bool disclaimerAccepted ;
54
-
55
- private readonly RenameServiceOptions settings = new ( ) ;
56
57
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
+ }
58
63
59
64
public async Task < RangeOrPlaceholderRange ? > PrepareRenameSymbol ( PrepareRenameParams request , CancellationToken cancellationToken )
60
65
{
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 ( )
67
67
{
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 ) ;
75
74
76
75
// 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 ;
82
82
}
83
83
84
84
public async Task < WorkspaceEdit ? > RenameSymbol ( RenameParams request , CancellationToken cancellationToken )
85
85
{
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 ; }
87
90
88
91
ScriptFile scriptFile = workspaceService . GetFile ( request . TextDocument . Uri ) ;
89
92
ScriptPositionAdapter position = request . Position ;
@@ -95,7 +98,7 @@ internal class RenameService(
95
98
TextEdit [ ] changes = tokenToRename switch
96
99
{
97
100
FunctionDefinitionAst or CommandAst => RenameFunction ( tokenToRename , scriptFile . ScriptAst , request ) ,
98
- VariableExpressionAst => RenameVariable ( tokenToRename , scriptFile . ScriptAst , request ) ,
101
+ VariableExpressionAst => RenameVariable ( tokenToRename , scriptFile . ScriptAst , request , options . createVariableAlias ) ,
99
102
// FIXME: Only throw if capability is not prepareprovider
100
103
_ => throw new InvalidOperationException ( "This should not happen as PrepareRename should have already checked for viability. File an issue if you see this." )
101
104
} ;
@@ -122,17 +125,16 @@ internal static TextEdit[] RenameFunction(Ast target, Ast scriptAst, RenameParam
122
125
return visitor . VisitAndGetEdits ( scriptAst ) ;
123
126
}
124
127
125
- internal TextEdit [ ] RenameVariable ( Ast symbol , Ast scriptAst , RenameParams requestParams )
128
+ internal static TextEdit [ ] RenameVariable ( Ast symbol , Ast scriptAst , RenameParams requestParams , bool createAlias )
126
129
{
127
130
if ( symbol is VariableExpressionAst or ParameterAst or CommandParameterAst or StringConstantExpressionAst )
128
131
{
129
-
130
132
IterativeVariableRename visitor = new (
131
133
requestParams . NewName ,
132
134
symbol . Extent . StartLineNumber ,
133
135
symbol . Extent . StartColumnNumber ,
134
136
scriptAst ,
135
- settings
137
+ createAlias
136
138
) ;
137
139
visitor . Visit ( scriptAst ) ;
138
140
return visitor . Modifications . ToArray ( ) ;
@@ -200,15 +202,10 @@ internal static ScriptExtentAdapter GetFunctionNameExtent(FunctionDefinitionAst
200
202
/// Prompts the user to accept the rename disclaimer.
201
203
/// </summary>
202
204
/// <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 )
204
206
{
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 ; }
212
209
213
210
// TODO: Localization
214
211
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
242
239
Type = MessageType . Info
243
240
} ;
244
241
lsp . SendNotification ( msgParams ) ;
245
- disclaimerDeclined = true ;
246
- return ! disclaimerDeclined ;
242
+ disclaimerDeclinedForSession = true ;
243
+ return ! disclaimerDeclinedForSession ;
247
244
}
248
245
if ( result . Title == acceptAnswer )
249
246
{
@@ -255,8 +252,8 @@ private async Task<bool> AcceptRenameDisclaimer(CancellationToken cancellationTo
255
252
} ;
256
253
lsp . SendNotification ( msgParams ) ;
257
254
258
- disclaimerAccepted = true ;
259
- return disclaimerAccepted ;
255
+ disclaimerAcceptedForSession = true ;
256
+ return disclaimerAcceptedForSession ;
260
257
}
261
258
// if (result.Title == acceptWorkspaceAnswer)
262
259
// {
@@ -514,7 +511,6 @@ public ScriptPositionAdapter(Position position) : this(position.Line + 1, positi
514
511
scriptPosition . position . LineNumber - 1 , scriptPosition . position . ColumnNumber - 1
515
512
) ;
516
513
517
-
518
514
public static implicit operator ScriptPositionAdapter ( ScriptPosition position ) => new ( position ) ;
519
515
public static implicit operator ScriptPosition ( ScriptPositionAdapter position ) => position ;
520
516
0 commit comments