Skip to content

Commit a3aa198

Browse files
committed
Split out RenameService. **TESTS NEED FIXING**
1 parent 14f395e commit a3aa198

File tree

5 files changed

+484
-276
lines changed

5 files changed

+484
-276
lines changed

src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeFunctionVistor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Collections.Generic;
55
using System.IO;
66
using System.Management.Automation.Language;
7-
using Microsoft.PowerShell.EditorServices.Handlers;
7+
using Microsoft.PowerShell.EditorServices.Services;
88
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
99

1010
namespace Microsoft.PowerShell.EditorServices.Refactoring

src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeVariableVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
using System.Collections.Generic;
55
using System.Management.Automation.Language;
6-
using Microsoft.PowerShell.EditorServices.Handlers;
76
using System.Linq;
87
using System;
98
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
9+
using Microsoft.PowerShell.EditorServices.Services;
1010

1111
namespace Microsoft.PowerShell.EditorServices.Refactoring
1212
{

src/PowerShellEditorServices/Services/PowerShell/Utility/IScriptExtentExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.Management.Automation.Language;
5-
using Microsoft.PowerShell.EditorServices.Handlers;
5+
using Microsoft.PowerShell.EditorServices.Services;
66

77
namespace PowerShellEditorServices.Services.PowerShell.Utility
88
{

src/PowerShellEditorServices/Services/TextDocument/Handlers/RenameHandler.cs

Lines changed: 12 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -2,305 +2,44 @@
22
// Licensed under the MIT License.
33
#nullable enable
44

5-
using System;
6-
using System.Collections.Generic;
7-
using System.Management.Automation.Language;
5+
86
using System.Threading;
97
using System.Threading.Tasks;
108
using Microsoft.PowerShell.EditorServices.Services;
11-
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
12-
using Microsoft.PowerShell.EditorServices.Refactoring;
9+
1310
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
1411
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1512
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
16-
using OmniSharp.Extensions.LanguageServer.Protocol;
17-
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
13+
1814

1915
namespace Microsoft.PowerShell.EditorServices.Handlers;
2016

2117
/// <summary>
2218
/// A handler for <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareRename">textDocument/prepareRename</a>
2319
/// LSP Ref: <see cref="PrepareRename()"/>
2420
/// </summary>
25-
internal class PrepareRenameHandler(WorkspaceService workspaceService, ILanguageServerFacade lsp, ILanguageServerConfiguration config) : IPrepareRenameHandler
21+
internal class PrepareRenameHandler
22+
(
23+
IRenameService renameService
24+
) : IPrepareRenameHandler
2625
{
2726
public RenameRegistrationOptions GetRegistrationOptions(RenameCapability capability, ClientCapabilities clientCapabilities) => capability.PrepareSupport ? new() { PrepareProvider = true } : new();
2827

2928
public async Task<RangeOrPlaceholderRange?> Handle(PrepareRenameParams request, CancellationToken cancellationToken)
30-
{
31-
// FIXME: Config actually needs to be read and implemented, this is to make the referencing satisfied
32-
config.ToString();
33-
ShowMessageRequestParams reqParams = new ShowMessageRequestParams
34-
{
35-
Type = MessageType.Warning,
36-
Message = "Test Send",
37-
Actions = new MessageActionItem[] {
38-
new MessageActionItem() { Title = "I Accept" },
39-
new MessageActionItem() { Title = "I Accept [Workspace]" },
40-
new MessageActionItem() { Title = "Decline" }
41-
}
42-
};
43-
44-
MessageActionItem result = await lsp.SendRequest(reqParams, cancellationToken).ConfigureAwait(false);
45-
if (result.Title == "Test Action")
46-
{
47-
// FIXME: Need to accept
48-
Console.WriteLine("yay");
49-
}
50-
51-
ScriptFile scriptFile = workspaceService.GetFile(request.TextDocument.Uri);
52-
53-
// 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 take rename actions inside the dotsourced file.
54-
if (Utilities.AssertContainsDotSourced(scriptFile.ScriptAst))
55-
{
56-
throw new HandlerErrorException("Dot Source detected, this is currently not supported");
57-
}
58-
59-
ScriptPositionAdapter position = request.Position;
60-
Ast target = FindRenamableSymbol(scriptFile, position);
61-
if (target is null) { return null; }
62-
return target switch
63-
{
64-
FunctionDefinitionAst funcAst => GetFunctionNameExtent(funcAst),
65-
_ => new ScriptExtentAdapter(target.Extent)
66-
};
67-
}
68-
69-
private static ScriptExtentAdapter GetFunctionNameExtent(FunctionDefinitionAst ast)
70-
{
71-
string name = ast.Name;
72-
// FIXME: Gather dynamically from the AST and include backticks and whatnot that might be present
73-
int funcLength = "function ".Length;
74-
ScriptExtentAdapter funcExtent = new(ast.Extent);
75-
76-
// Get a range that represents only the function name
77-
return funcExtent with
78-
{
79-
Start = funcExtent.Start.Delta(0, funcLength),
80-
End = funcExtent.Start.Delta(0, funcLength + name.Length)
81-
};
82-
}
83-
84-
/// <summary>
85-
/// Finds a renamable symbol at a given position in a script file.
86-
/// </summary>
87-
/// <returns>Ast of the token or null if no renamable symbol was found</returns>
88-
internal static Ast FindRenamableSymbol(ScriptFile scriptFile, ScriptPositionAdapter position)
89-
{
90-
int line = position.Line;
91-
int column = position.Column;
92-
93-
// Cannot use generic here as our desired ASTs do not share a common parent
94-
Ast token = scriptFile.ScriptAst.Find(ast =>
95-
{
96-
// Skip all statements that end before our target line or start after our target line. This is a performance optimization.
97-
if (ast.Extent.EndLineNumber < line || ast.Extent.StartLineNumber > line) { return false; }
98-
99-
// Supported types, filters out scriptblocks and whatnot
100-
if (ast is not (
101-
FunctionDefinitionAst
102-
or VariableExpressionAst
103-
or CommandParameterAst
104-
or ParameterAst
105-
or StringConstantExpressionAst
106-
or CommandAst
107-
))
108-
{
109-
return false;
110-
}
111-
112-
// Special detection for Function calls that dont follow verb-noun syntax e.g. DoThing
113-
// It's not foolproof but should work in most cases where it is explicit (e.g. not & $x)
114-
if (ast is StringConstantExpressionAst stringAst)
115-
{
116-
if (stringAst.Parent is not CommandAst parent) { return false; }
117-
if (parent.GetCommandName() != stringAst.Value) { return false; }
118-
}
119-
120-
ScriptExtentAdapter target = ast switch
121-
{
122-
FunctionDefinitionAst funcAst => GetFunctionNameExtent(funcAst),
123-
_ => new ScriptExtentAdapter(ast.Extent)
124-
};
125-
126-
return target.Contains(position);
127-
}, true);
128-
129-
return token;
130-
}
29+
=> await renameService.PrepareRenameSymbol(request, cancellationToken).ConfigureAwait(false);
13130
}
13231

13332
/// <summary>
13433
/// A handler for textDocument/prepareRename
13534
/// <para />LSP Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename
13635
/// </summary>
137-
internal class RenameHandler(WorkspaceService workspaceService) : IRenameHandler
36+
internal class RenameHandler(
37+
IRenameService renameService
38+
) : IRenameHandler
13839
{
13940
// RenameOptions may only be specified if the client states that it supports prepareSupport in its initial initialize request.
14041
public RenameRegistrationOptions GetRegistrationOptions(RenameCapability capability, ClientCapabilities clientCapabilities) => capability.PrepareSupport ? new() { PrepareProvider = true } : new();
14142

14243
public async Task<WorkspaceEdit?> Handle(RenameParams request, CancellationToken cancellationToken)
143-
{
144-
ScriptFile scriptFile = workspaceService.GetFile(request.TextDocument.Uri);
145-
ScriptPositionAdapter position = request.Position;
146-
147-
Ast tokenToRename = PrepareRenameHandler.FindRenamableSymbol(scriptFile, position);
148-
if (tokenToRename is null) { return null; }
149-
150-
// TODO: Potentially future cross-file support
151-
TextEdit[] changes = tokenToRename switch
152-
{
153-
FunctionDefinitionAst or CommandAst => RenameFunction(tokenToRename, scriptFile.ScriptAst, request),
154-
VariableExpressionAst => RenameVariable(tokenToRename, scriptFile.ScriptAst, request),
155-
// FIXME: Only throw if capability is not prepareprovider
156-
_ => throw new HandlerErrorException("This should not happen as PrepareRename should have already checked for viability. File an issue if you see this.")
157-
};
158-
159-
return new WorkspaceEdit
160-
{
161-
Changes = new Dictionary<DocumentUri, IEnumerable<TextEdit>>
162-
{
163-
[request.TextDocument.Uri] = changes
164-
}
165-
};
166-
}
167-
168-
// TODO: We can probably merge these two methods with Generic Type constraints since they are factored into overloading
169-
170-
internal static TextEdit[] RenameFunction(Ast token, Ast scriptAst, RenameParams renameParams)
171-
{
172-
ScriptPositionAdapter position = renameParams.Position;
173-
174-
string tokenName = "";
175-
if (token is FunctionDefinitionAst funcDef)
176-
{
177-
tokenName = funcDef.Name;
178-
}
179-
else if (token.Parent is CommandAst CommAst)
180-
{
181-
tokenName = CommAst.GetCommandName();
182-
}
183-
IterativeFunctionRename visitor = new(
184-
tokenName,
185-
renameParams.NewName,
186-
position.Line,
187-
position.Column,
188-
scriptAst
189-
);
190-
visitor.Visit(scriptAst);
191-
return visitor.Modifications.ToArray();
192-
}
193-
194-
internal static TextEdit[] RenameVariable(Ast symbol, Ast scriptAst, RenameParams requestParams)
195-
{
196-
if (symbol is VariableExpressionAst or ParameterAst or CommandParameterAst or StringConstantExpressionAst)
197-
{
198-
199-
IterativeVariableRename visitor = new(
200-
requestParams.NewName,
201-
symbol.Extent.StartLineNumber,
202-
symbol.Extent.StartColumnNumber,
203-
scriptAst,
204-
null //FIXME: Pass through Alias config
205-
);
206-
visitor.Visit(scriptAst);
207-
return visitor.Modifications.ToArray();
208-
209-
}
210-
return [];
211-
}
212-
}
213-
214-
public class RenameSymbolOptions
215-
{
216-
public bool CreateAlias { get; set; }
217-
}
218-
219-
/// <summary>
220-
/// Represents a position in a script file that adapts and implicitly converts based on context. PowerShell script lines/columns start at 1, but LSP textdocument lines/columns start at 0. The default line/column constructor is 1-based.
221-
/// </summary>
222-
public record ScriptPositionAdapter(IScriptPosition position) : IScriptPosition, IComparable<ScriptPositionAdapter>, IComparable<Position>, IComparable<ScriptPosition>
223-
{
224-
public int Line => position.LineNumber;
225-
public int Column => position.ColumnNumber;
226-
public int Character => position.ColumnNumber;
227-
public int LineNumber => position.LineNumber;
228-
public int ColumnNumber => position.ColumnNumber;
229-
230-
public string File => position.File;
231-
string IScriptPosition.Line => position.Line;
232-
public int Offset => position.Offset;
233-
234-
public ScriptPositionAdapter(int Line, int Column) : this(new ScriptPosition(null, Line, Column, null)) { }
235-
public ScriptPositionAdapter(ScriptPosition position) : this((IScriptPosition)position) { }
236-
237-
public ScriptPositionAdapter(Position position) : this(position.Line + 1, position.Character + 1) { }
238-
public static implicit operator ScriptPositionAdapter(Position position) => new(position);
239-
public static implicit operator Position(ScriptPositionAdapter scriptPosition) => new
240-
(
241-
scriptPosition.position.LineNumber - 1, scriptPosition.position.ColumnNumber - 1
242-
);
243-
244-
245-
public static implicit operator ScriptPositionAdapter(ScriptPosition position) => new(position);
246-
public static implicit operator ScriptPosition(ScriptPositionAdapter position) => position;
247-
248-
internal ScriptPositionAdapter Delta(int LineAdjust, int ColumnAdjust) => new(
249-
position.LineNumber + LineAdjust,
250-
position.ColumnNumber + ColumnAdjust
251-
);
252-
253-
public int CompareTo(ScriptPositionAdapter other)
254-
{
255-
if (position.LineNumber == other.position.LineNumber)
256-
{
257-
return position.ColumnNumber.CompareTo(other.position.ColumnNumber);
258-
}
259-
return position.LineNumber.CompareTo(other.position.LineNumber);
260-
}
261-
public int CompareTo(Position other) => CompareTo((ScriptPositionAdapter)other);
262-
public int CompareTo(ScriptPosition other) => CompareTo((ScriptPositionAdapter)other);
263-
public string GetFullScript() => throw new NotImplementedException();
264-
}
265-
266-
/// <summary>
267-
/// Represents a range in a script file that adapts and implicitly converts based on context. PowerShell script lines/columns start at 1, but LSP textdocument lines/columns start at 0. The default ScriptExtent constructor is 1-based
268-
/// </summary>
269-
/// <param name="extent"></param>
270-
internal record ScriptExtentAdapter(IScriptExtent extent) : IScriptExtent
271-
{
272-
public ScriptPositionAdapter Start = new(extent.StartScriptPosition);
273-
public ScriptPositionAdapter End = new(extent.EndScriptPosition);
274-
275-
public static implicit operator ScriptExtentAdapter(ScriptExtent extent) => new(extent);
276-
277-
public static implicit operator ScriptExtentAdapter(Range range) => new(new ScriptExtent(
278-
// Will get shifted to 1-based
279-
new ScriptPositionAdapter(range.Start),
280-
new ScriptPositionAdapter(range.End)
281-
));
282-
public static implicit operator Range(ScriptExtentAdapter adapter) => new()
283-
{
284-
// Will get shifted to 0-based
285-
Start = adapter.Start,
286-
End = adapter.End
287-
};
288-
289-
public static implicit operator ScriptExtent(ScriptExtentAdapter adapter) => adapter;
290-
291-
public static implicit operator RangeOrPlaceholderRange(ScriptExtentAdapter adapter) => new((Range)adapter);
292-
293-
public IScriptPosition StartScriptPosition => Start;
294-
public IScriptPosition EndScriptPosition => End;
295-
public int EndColumnNumber => End.ColumnNumber;
296-
public int EndLineNumber => End.LineNumber;
297-
public int StartOffset => extent.EndOffset;
298-
public int EndOffset => extent.EndOffset;
299-
public string File => extent.File;
300-
public int StartColumnNumber => extent.StartColumnNumber;
301-
public int StartLineNumber => extent.StartLineNumber;
302-
public string Text => extent.Text;
303-
304-
public bool Contains(Position position) => ContainsPosition(this, position);
305-
public static bool ContainsPosition(ScriptExtentAdapter range, ScriptPositionAdapter position) => Range.ContainsPosition(range, position);
44+
=> await renameService.RenameSymbol(request, cancellationToken).ConfigureAwait(false);
30645
}

0 commit comments

Comments
 (0)