Skip to content

Commit d4b40d8

Browse files
Add extract function and general changes
- Port ExtractFunctionDefinition from PowerShell - Add ability to set document path for DocumentEditWriter. This will tag all edits it creates with the path. The document edit processor will then be able to route any tagged edits to the correct file - Add span support to the DocumentEditWriter. Outside of Core 2.1 performance may be a small net loss, but it's easier to work with and should be a sizable boost in 2.1+ - Refactor Empty language object construction - Add input prompt handling in IRefactorUI
1 parent 1a1a621 commit d4b40d8

File tree

80 files changed

+4566
-827
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+4566
-827
lines changed

EditorServicesCommandSuite.build.ps1

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ task Clean {
3939
}
4040

4141
New-Item -ItemType Directory $releaseFolder | Out-Null
42-
New-Item -ItemType Directory $releaseFolder/RefactorCmdlets | Out-Null
4342
}
4443

4544
task BuildDocs -If { $Discovery.HasDocs } {
@@ -93,12 +92,12 @@ task BuildManaged {
9392

9493
task BuildRefactorModule {
9594
$releaseFolder = $Folders.Release
96-
$dllToImport = "$PSScriptRoot/src/EditorServicesCommandSuite/bin/$Configuration/netstandard2.0/EditorServicesCommandSuite.dll"
95+
$dllToImport = "$PSScriptRoot/src/EditorServicesCommandSuite/bin/$Configuration/netstandard2.0/publish/EditorServicesCommandSuite.dll"
9796

9897
$script = {
9998
Add-Type -Path '{0}'
10099
[EditorServicesCommandSuite.Internal.CommandSuite]::WriteRefactorModule('{1}')
101-
}.ToString() -f $dllToImport, "$releaseFolder\RefactorCmdlets\RefactorCmdlets.cdxml"
100+
}.ToString() -f $dllToImport, "$releaseFolder\EditorServicesCommandSuite.RefactorCmdlets.cdxml"
102101

103102
$encodedScript = [convert]::ToBase64String(
104103
[System.Text.Encoding]::Unicode.GetBytes($script))
@@ -125,6 +124,9 @@ task CopyToRelease {
125124
Copy-Item $PSScriptRoot/src/EditorServicesCommandSuite.EditorServices/bin/$Configuration/netstandard2.0/publish/EditorServicesCommandSuite.* -Destination $releaseFolder
126125
Copy-Item $PSScriptRoot/src/EditorServicesCommandSuite.PSReadLine/bin/$Configuration/netstandard2.0/publish/EditorServicesCommandSuite.* -Destination $releaseFolder
127126
Copy-Item $PSScriptRoot/src/EditorServicesCommandSuite.PSReadLine/bin/$Configuration/netstandard2.0/publish/System.Buffers.dll -Destination $releaseFolder
127+
Copy-Item $PSScriptRoot/src/EditorServicesCommandSuite.PSReadLine/bin/$Configuration/netstandard2.0/publish/System.Memory.dll -Destination $releaseFolder
128+
Copy-Item $PSScriptRoot/src/EditorServicesCommandSuite.PSReadLine/bin/$Configuration/netstandard2.0/publish/System.Numerics.Vectors.dll -Destination $releaseFolder
129+
Copy-Item $PSScriptRoot/src/EditorServicesCommandSuite.PSReadLine/bin/$Configuration/netstandard2.0/publish/System.Runtime.CompilerServices.Unsafe.dll -Destination $releaseFolder
128130
}
129131

130132
task Analyze -If { $Settings.ShouldAnalyze } {

module/EditorServicesCommandSuite.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Import-Module $PSScriptRoot/EditorServicesCommandSuite.dll
2-
Import-Module $PSScriptRoot/RefactorCmdlets/RefactorCmdlets.cdxml
2+
Import-Module $PSScriptRoot/EditorServicesCommandSuite.RefactorCmdlets.cdxml
33

44
if (-not $CommandSuite -or $CommandSuite -isnot [EditorServicesCommandSuite.Internal.CommandSuite]) {
55
$IsMainRunspace = $true
Lines changed: 129 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
35
using System.Linq;
6+
using System.Runtime.InteropServices;
7+
using System.Text;
8+
using System.Threading;
49
using System.Threading.Tasks;
10+
using System.Web;
511
using EditorServicesCommandSuite.Internal;
612
using Microsoft.PowerShell.EditorServices;
713
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
@@ -10,6 +16,8 @@ namespace EditorServicesCommandSuite.EditorServices
1016
{
1117
internal class DocumentService : IDocumentEditProcessor
1218
{
19+
private const string FileUriPrefix = "file:///";
20+
1321
private readonly EditorSession _editorSession;
1422

1523
private readonly MessageService _messages;
@@ -20,33 +28,51 @@ internal DocumentService(EditorSession editorSession, MessageService messages)
2028
_messages = messages;
2129
}
2230

23-
public async Task WriteDocumentEditsAsync(IEnumerable<DocumentEdit> edits)
31+
public async Task WriteDocumentEditsAsync(IEnumerable<DocumentEdit> edits, CancellationToken cancellationToken)
2432
{
25-
var context = await _messages.SendRequestAsync(
26-
GetEditorContextRequest.Type,
27-
new GetEditorContextRequest(),
28-
waitForResponse: true);
33+
ClientEditorContext context = await GetClientContext();
34+
35+
// Order by empty file names first so the first group processed is the current file.
36+
IOrderedEnumerable<IGrouping<string, DocumentEdit>> orderedGroups = edits
37+
.GroupBy(e => e.FileName)
38+
.OrderByDescending(g => string.IsNullOrEmpty(g.Key));
2939

30-
var scriptFile = _editorSession.Workspace.GetFile(context.CurrentFilePath);
31-
foreach (var edit in edits.OrderByDescending(edit => edit.StartOffset))
40+
foreach (IGrouping<string, DocumentEdit> editGroup in orderedGroups)
3241
{
33-
var request = new InsertTextRequest()
42+
ScriptFile scriptFile;
43+
try
3444
{
35-
FilePath = scriptFile.ClientFilePath,
36-
InsertText = edit.NewValue,
37-
InsertRange = new Range()
45+
scriptFile = _editorSession.Workspace.GetFile(
46+
string.IsNullOrEmpty(editGroup.Key) ? context.CurrentFilePath : editGroup.Key);
47+
}
48+
catch (FileNotFoundException)
49+
{
50+
scriptFile = await CreateNewFile(context, editGroup.Key, cancellationToken);
51+
}
52+
53+
// ScriptFile.ClientFilePath isn't always a URI.
54+
string clientFilePath = GetPathAsClientPath(scriptFile.ClientFilePath);
55+
foreach (var edit in editGroup.OrderByDescending(edit => edit.StartOffset))
56+
{
57+
cancellationToken.ThrowIfCancellationRequested();
58+
var request = new InsertTextRequest()
3859
{
39-
Start = ToServerPosition(scriptFile.GetPositionAtOffset((int)edit.StartOffset)),
40-
End = ToServerPosition(scriptFile.GetPositionAtOffset((int)edit.EndOffset)),
41-
},
42-
};
60+
FilePath = clientFilePath,
61+
InsertText = edit.NewValue,
62+
InsertRange = new Range()
63+
{
64+
Start = ToServerPosition(scriptFile.GetPositionAtOffset((int)edit.StartOffset)),
65+
End = ToServerPosition(scriptFile.GetPositionAtOffset((int)edit.EndOffset)),
66+
},
67+
};
4368

44-
await _messages.SendRequestAsync(
45-
InsertTextRequest.Type,
46-
request,
47-
waitForResponse: true);
69+
await _messages.SendRequestAsync(
70+
InsertTextRequest.Type,
71+
request,
72+
waitForResponse: true);
4873

49-
await Task.Delay(TimeSpan.FromMilliseconds(50));
74+
await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken);
75+
}
5076
}
5177
}
5278

@@ -58,5 +84,88 @@ internal static Position ToServerPosition(BufferPosition position)
5884
Character = position.Column - 1,
5985
};
6086
}
87+
88+
private static string GetPathAsClientPath(string path)
89+
{
90+
Debug.Assert(
91+
!string.IsNullOrWhiteSpace(path),
92+
"Caller should verify path is valid");
93+
94+
if (path.StartsWith("file:///", StringComparison.Ordinal))
95+
{
96+
return path;
97+
}
98+
99+
Debug.Assert(
100+
Path.IsPathRooted(path),
101+
"EditorServices saved an unrooted, non-URI path to ClientFilePath");
102+
103+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
104+
{
105+
return new Uri(path).AbsoluteUri;
106+
}
107+
108+
// VSCode file URIs on Windows need the drive letter lowercase, and the colon
109+
// URI encoded. System.Uri won't do that, so we manually create the URI.
110+
var newUri = new StringBuilder(HttpUtility.UrlPathEncode(path));
111+
int colonIndex = path.IndexOf(Symbols.Colon);
112+
for (var i = colonIndex - 1; i >= 0; i--)
113+
{
114+
newUri.Remove(i, 1);
115+
newUri.Insert(i, char.ToLowerInvariant(path[i]));
116+
}
117+
118+
return newUri
119+
.Remove(colonIndex, 1)
120+
.Insert(colonIndex, "%3A")
121+
.Replace(Symbols.Backslash, Symbols.ForwardSlash)
122+
.Insert(0, FileUriPrefix)
123+
.ToString();
124+
}
125+
126+
private async Task<ScriptFile> CreateNewFile(
127+
ClientEditorContext context,
128+
string path,
129+
CancellationToken cancellationToken)
130+
{
131+
// Path parameter doesn't actually do anything currently. The new file will be untitled.
132+
await _messages.SendRequestAsync(
133+
NewFileRequest.Type,
134+
path,
135+
true);
136+
137+
ClientEditorContext newContext;
138+
while (true)
139+
{
140+
newContext = await GetClientContext();
141+
if (!newContext.CurrentFilePath.Equals(context.CurrentFilePath, StringComparison.OrdinalIgnoreCase))
142+
{
143+
break;
144+
}
145+
146+
await Task.Delay(200, cancellationToken);
147+
}
148+
149+
ScriptFile scriptFile = _editorSession.Workspace.GetFile(newContext.CurrentFilePath);
150+
await _messages.SendRequestAsync(
151+
SaveFileRequest.Type,
152+
new SaveFileDetails()
153+
{
154+
FilePath = scriptFile.ClientFilePath,
155+
NewPath = path,
156+
},
157+
waitForResponse: true);
158+
159+
cancellationToken.ThrowIfCancellationRequested();
160+
return _editorSession.Workspace.GetFile(path);
161+
}
162+
163+
private async Task<ClientEditorContext> GetClientContext()
164+
{
165+
return await _messages.SendRequestAsync(
166+
GetEditorContextRequest.Type,
167+
new GetEditorContextRequest(),
168+
waitForResponse: true);
169+
}
61170
}
62171
}
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
2+
<PropertyGroup>
3+
<FileUpgradeFlags>
4+
</FileUpgradeFlags>
5+
<UpgradeBackupLocation>
6+
</UpgradeBackupLocation>
7+
<OldToolsVersion>2.0</OldToolsVersion>
8+
</PropertyGroup>
29
<Import Project="..\EditorServicesCommandSuite.Common.props" />
310
<ItemGroup>
411
<ProjectReference Include="..\EditorServicesCommandSuite\EditorServicesCommandSuite.csproj" />
@@ -9,5 +16,4 @@
916
<HintPath>..\..\lib\PowerShellEditorServices\bin\Core\Microsoft.PowerShell.EditorServices.Protocol.dll</HintPath>
1017
</Reference>
1118
</ItemGroup>
12-
</Project>
13-
19+
</Project>

src/EditorServicesCommandSuite.EditorServices/Internal/CommandSuite.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Diagnostics;
14
using System.Management.Automation;
25
using System.Management.Automation.Host;
36
using EditorServicesCommandSuite.Internal;
@@ -11,6 +14,7 @@ namespace EditorServicesCommandSuite.EditorServices.Internal
1114
/// Provides a central entry point for interacting with a Editor Services based command
1215
/// suite session.
1316
/// </summary>
17+
[EditorBrowsable(EditorBrowsableState.Never), DebuggerStepThrough]
1418
public class CommandSuite : EditorServicesCommandSuite.Internal.CommandSuite
1519
{
1620
private const string EditorOperationsFieldName = "editorOperations";
@@ -106,6 +110,7 @@ private CommandSuite(
106110
/// <returns>
107111
/// The command suite instance for the process.
108112
/// </returns>
113+
[Obsolete("do not use this method", error: true), EditorBrowsable(EditorBrowsableState.Never)]
109114
public static CommandSuite GetCommandSuite(
110115
EditorObject psEditor,
111116
EngineIntrinsics engine,

src/EditorServicesCommandSuite.EditorServices/UIService.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ await _messages.Sender.SendRequest(
4040
.ConfigureAwait(continueOnCapturedContext: false);
4141
}
4242

43+
public async Task<string> ShowInputPromptAsync(
44+
string caption,
45+
string message,
46+
bool waitForResponse)
47+
{
48+
ShowInputPromptResponse response = await _messages.Sender.SendRequest(
49+
ShowInputPromptRequest.Type,
50+
new ShowInputPromptRequest()
51+
{
52+
Name = caption,
53+
Label = message,
54+
},
55+
waitForResponse)
56+
.ConfigureAwait(continueOnCapturedContext: false);
57+
58+
if (response == null || response.PromptCancelled)
59+
{
60+
throw new OperationCanceledException();
61+
}
62+
63+
return response.ResponseText;
64+
}
65+
4366
public async Task<TItem> ShowChoicePromptAsync<TItem>(
4467
string caption,
4568
string message,

src/EditorServicesCommandSuite.EditorServices/WorkspaceService.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using System.Management.Automation;
3+
using System.Management.Automation.Language;
24
using EditorServicesCommandSuite.Internal;
35
using Microsoft.PowerShell.EditorServices;
46

@@ -20,5 +22,11 @@ public override bool IsUntitledWorkspace()
2022
{
2123
return string.IsNullOrEmpty(_workspace.WorkspacePath);
2224
}
25+
26+
protected override Tuple<ScriptBlockAst, Token[]> GetFileContext(string path)
27+
{
28+
ScriptFile scriptFile = _workspace.GetFile(path);
29+
return Tuple.Create(scriptFile.ScriptAst, scriptFile.ScriptTokens);
30+
}
2331
}
2432
}

0 commit comments

Comments
 (0)