Skip to content

Commit 22f17e3

Browse files
committed
Add RemoteFileManager to manage remote files
This change adds a new RemoteFileManager class which manages files which are opened when a remote session is active. It handles the gathering of remote file contents and mapping remote files paths to a local cache. It also keeps track of the remote files which were opened in the editor and closes them once the remote session has been exited.
1 parent 5fe47a4 commit 22f17e3

File tree

16 files changed

+414
-132
lines changed

16 files changed

+414
-132
lines changed

src/PowerShellEditorServices.Channel.WebSocket/WebsocketServerChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public class DebugAdapterWebSocketConnection : EditorServiceWebSocketConnection
151151
{
152152
public DebugAdapterWebSocketConnection()
153153
{
154-
Server = new DebugAdapter(null, null, Channel);
154+
Server = new DebugAdapter(null, null, Channel, null);
155155
}
156156
}
157157
}

src/PowerShellEditorServices.Host/EditorServicesHost.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ public void StartDebugService(int debugServicePort, ProfilePaths profilePaths)
164164
new DebugAdapter(
165165
hostDetails,
166166
profilePaths,
167-
new TcpSocketServerChannel(debugServicePort));
167+
new TcpSocketServerChannel(debugServicePort),
168+
this.languageServer?.EditorOperations);
168169

169170
this.debugAdapter.SessionEnded +=
170171
(obj, args) =>

src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ public static readonly
108108
RequestType<string, EditorCommandResponse>.Create("editor/openFile");
109109
}
110110

111+
public class CloseFileRequest
112+
{
113+
public static readonly
114+
RequestType<string, EditorCommandResponse> Type =
115+
RequestType<string, EditorCommandResponse>.Create("editor/closeFile");
116+
}
117+
111118
public class ShowInformationMessageRequest
112119
{
113120
public static readonly

src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55

66
using Microsoft.PowerShell.EditorServices.Debugging;
7+
using Microsoft.PowerShell.EditorServices.Extensions;
78
using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
89
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
910
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
@@ -23,21 +24,26 @@ public class DebugAdapter : DebugAdapterBase
2324
{
2425
private EditorSession editorSession;
2526
private OutputDebouncer outputDebouncer;
27+
2628
private bool noDebug;
2729
private bool waitingForAttach;
2830
private string scriptPathToLaunch;
2931
private string arguments;
3032

3133
public DebugAdapter(HostDetails hostDetails, ProfilePaths profilePaths)
32-
: this(hostDetails, profilePaths, new StdioServerChannel())
34+
: this(hostDetails, profilePaths, new StdioServerChannel(), null)
3335
{
3436
}
3537

36-
public DebugAdapter(HostDetails hostDetails, ProfilePaths profilePaths, ChannelBase serverChannel)
38+
public DebugAdapter(
39+
HostDetails hostDetails,
40+
ProfilePaths profilePaths,
41+
ChannelBase serverChannel,
42+
IEditorOperations editorOperations)
3743
: base(serverChannel)
3844
{
3945
this.editorSession = new EditorSession();
40-
this.editorSession.StartDebugSession(hostDetails, profilePaths);
46+
this.editorSession.StartDebugSession(hostDetails, profilePaths, editorOperations);
4147
this.editorSession.PowerShellContext.RunspaceChanged += this.powerShellContext_RunspaceChanged;
4248
this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped;
4349
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
@@ -184,7 +190,7 @@ protected async Task HandleLaunchRequest(
184190

185191
// If no script is being launched, execute an empty script to
186192
// cause the prompt string to be evaluated and displayed
187-
if (!string.IsNullOrEmpty(this.scriptPathToLaunch))
193+
if (string.IsNullOrEmpty(this.scriptPathToLaunch))
188194
{
189195
await this.editorSession.PowerShellContext.ExecuteScriptString(
190196
"", false, true);

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ public class LanguageServer : LanguageServerBase
3232
private OutputDebouncer outputDebouncer;
3333
private LanguageServerEditorOperations editorOperations;
3434
private LanguageServerSettings currentSettings = new LanguageServerSettings();
35+
3536
private Dictionary<string, Dictionary<string, MarkerCorrection>> codeActionsPerFile =
3637
new Dictionary<string, Dictionary<string, MarkerCorrection>>();
3738

39+
public IEditorOperations EditorOperations
40+
{
41+
get { return this.editorOperations; }
42+
}
43+
3844
/// <param name="hostDetails">
3945
/// Provides details about the host application.
4046
/// </param>

src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ public Task OpenFile(string filePath)
110110
true);
111111
}
112112

113+
public Task CloseFile(string filePath)
114+
{
115+
return
116+
this.messageSender.SendRequest(
117+
CloseFileRequest.Type,
118+
filePath,
119+
true);
120+
}
121+
113122
public string GetWorkspacePath()
114123
{
115124
return this.editorSession.Workspace.WorkspacePath;

src/PowerShellEditorServices/Debugging/DebugService.cs

Lines changed: 42 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,12 @@ public class DebugService
2828
private const string PsesGlobalVariableNamePrefix = "__psEditorServices_";
2929

3030
private PowerShellContext powerShellContext;
31+
private RemoteFileManager remoteFileManager;
3132

3233
// TODO: This needs to be managed per nested session
3334
private Dictionary<string, List<Breakpoint>> breakpointsPerFile =
3435
new Dictionary<string, List<Breakpoint>>();
3536

36-
private Dictionary<RunspaceDetails, Dictionary<string, string>> remoteFileMappings =
37-
new Dictionary<RunspaceDetails, Dictionary<string, string>>();
38-
3937
private int nextVariableId;
4038
private List<VariableDetailsBase> variables;
4139
private VariableContainerDetails globalScopeVariables;
@@ -56,12 +54,31 @@ public class DebugService
5654
/// The PowerShellContext to use for all debugging operations.
5755
/// </param>
5856
public DebugService(PowerShellContext powerShellContext)
57+
: this(powerShellContext, null)
58+
{
59+
}
60+
61+
/// <summary>
62+
/// Initializes a new instance of the DebugService class and uses
63+
/// the given PowerShellContext for all future operations.
64+
/// </summary>
65+
/// <param name="powerShellContext">
66+
/// The PowerShellContext to use for all debugging operations.
67+
/// </param>
68+
/// <param name="remoteFileManager">
69+
/// A RemoteFileManager instance to use for accessing files in remote sessions.
70+
/// </param>
71+
public DebugService(
72+
PowerShellContext powerShellContext,
73+
RemoteFileManager remoteFileManager)
5974
{
60-
Validate.IsNotNull("powerShellContext", powerShellContext);
75+
Validate.IsNotNull(nameof(powerShellContext), powerShellContext);
6176

6277
this.powerShellContext = powerShellContext;
6378
this.powerShellContext.DebuggerStop += this.OnDebuggerStop;
6479
this.powerShellContext.BreakpointUpdated += this.OnBreakpointUpdated;
80+
81+
this.remoteFileManager = remoteFileManager;
6582
}
6683

6784
#endregion
@@ -91,12 +108,13 @@ public async Task<BreakpointDetails[]> SetLineBreakpoints(
91108
{
92109
// Make sure we're using the remote script path
93110
string scriptPath = scriptFile.FilePath;
94-
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote)
111+
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
112+
this.remoteFileManager != null)
95113
{
96114
string mappedPath =
97-
this.GetRemotePathMapping(
98-
this.powerShellContext.CurrentRunspace,
99-
scriptPath);
115+
this.remoteFileManager.GetMappedPath(
116+
scriptPath,
117+
this.powerShellContext.CurrentRunspace);
100118

101119
if (mappedPath == null)
102120
{
@@ -764,13 +782,14 @@ private async Task FetchStackFrames()
764782
StackFrameDetails.Create(callStackFrames[i], autoVariables, localVariables);
765783

766784
string stackFrameScriptPath = this.stackFrameDetails[i].ScriptPath;
767-
if (this.powerShellContext.CurrentRunspace.Location == Session.RunspaceLocation.Remote &&
785+
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
786+
this.remoteFileManager != null &&
768787
!string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath))
769788
{
770789
this.stackFrameDetails[i].ScriptPath =
771-
Workspace.MapRemotePathToLocal(
790+
this.remoteFileManager.GetMappedPath(
772791
stackFrameScriptPath,
773-
this.powerShellContext.CurrentRunspace.ConnectionString);
792+
this.powerShellContext.CurrentRunspace);
774793
}
775794
}
776795
}
@@ -960,34 +979,6 @@ private string FormatInvalidBreakpointConditionMessage(string condition, string
960979
return $"'{condition}' is not a valid PowerShell expression. {message}";
961980
}
962981

963-
private void AddRemotePathMapping(RunspaceDetails runspaceDetails, string remotePath, string localPath)
964-
{
965-
Dictionary<string, string> runspaceMappings = null;
966-
967-
if (!this.remoteFileMappings.TryGetValue(runspaceDetails, out runspaceMappings))
968-
{
969-
runspaceMappings = new Dictionary<string, string>();
970-
this.remoteFileMappings.Add(runspaceDetails, runspaceMappings);
971-
}
972-
973-
// Add mappings in both directions
974-
runspaceMappings[localPath.ToLower()] = remotePath;
975-
runspaceMappings[remotePath.ToLower()] = localPath;
976-
}
977-
978-
private string GetRemotePathMapping(RunspaceDetails runspaceDetails, string localPath)
979-
{
980-
string remotePath = null;
981-
Dictionary<string, string> runspaceMappings = null;
982-
983-
if (this.remoteFileMappings.TryGetValue(runspaceDetails, out runspaceMappings))
984-
{
985-
runspaceMappings.TryGetValue(localPath.ToLower(), out remotePath);
986-
}
987-
988-
return remotePath;
989-
}
990-
991982
#endregion
992983

993984
#region Events
@@ -1003,56 +994,14 @@ private async void OnDebuggerStop(object sender, DebuggerStopEventArgs e)
1003994
await this.FetchStackFramesAndVariables();
1004995

1005996
// If this is a remote connection, get the file content
1006-
string localScriptPath = null;
1007-
if (this.powerShellContext.CurrentRunspace.Location == Session.RunspaceLocation.Remote)
997+
string localScriptPath = e.InvocationInfo.ScriptName;
998+
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
999+
this.remoteFileManager != null)
10081000
{
1009-
string remoteScriptPath = e.InvocationInfo.ScriptName;
1010-
if (!string.IsNullOrEmpty(remoteScriptPath))
1011-
{
1012-
localScriptPath =
1013-
Workspace.MapRemotePathToLocal(
1014-
remoteScriptPath,
1015-
this.powerShellContext.CurrentRunspace.ConnectionString);
1016-
1017-
// TODO: Catch filesystem exceptions!
1018-
1019-
// Does the local file already exist?
1020-
if (!File.Exists(localScriptPath))
1021-
{
1022-
// Load the file contents from the remote machine and create the buffer
1023-
PSCommand command = new PSCommand();
1024-
command.AddCommand("Microsoft.PowerShell.Management\\Get-Content");
1025-
command.AddParameter("Path", remoteScriptPath);
1026-
command.AddParameter("Raw");
1027-
command.AddParameter("Encoding", "Byte");
1028-
1029-
byte[] fileContent =
1030-
(await this.powerShellContext.ExecuteCommand<byte[]>(command, false, false))
1031-
.FirstOrDefault();
1032-
1033-
if (fileContent != null)
1034-
{
1035-
File.WriteAllBytes(localScriptPath, fileContent);
1036-
}
1037-
else
1038-
{
1039-
Logger.Write(
1040-
LogLevel.Warning,
1041-
$"Could not load contents of remote file '{remoteScriptPath}'");
1042-
}
1043-
1044-
// Add the file mapping so that breakpoints can be passed through
1045-
// to the real file in the remote session
1046-
this.AddRemotePathMapping(
1047-
this.powerShellContext.CurrentRunspace,
1048-
remoteScriptPath,
1049-
localScriptPath);
1050-
this.AddRemotePathMapping(
1051-
this.powerShellContext.CurrentRunspace,
1052-
localScriptPath,
1053-
remoteScriptPath);
1054-
}
1055-
}
1001+
localScriptPath =
1002+
await this.remoteFileManager.FetchRemoteFile(
1003+
e.InvocationInfo.ScriptName,
1004+
this.powerShellContext.CurrentRunspace);
10561005
}
10571006

10581007
// Notify the host that the debugger is stopped
@@ -1082,12 +1031,13 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
10821031
List<Breakpoint> breakpoints;
10831032

10841033
string scriptPath = lineBreakpoint.Script;
1085-
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote)
1034+
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
1035+
this.remoteFileManager != null)
10861036
{
10871037
string mappedPath =
1088-
this.GetRemotePathMapping(
1089-
this.powerShellContext.CurrentRunspace,
1090-
scriptPath);
1038+
this.remoteFileManager.GetMappedPath(
1039+
scriptPath,
1040+
this.powerShellContext.CurrentRunspace);
10911041

10921042
if (mappedPath == null)
10931043
{

src/PowerShellEditorServices/Extensions/IEditorOperations.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ public interface IEditorOperations
4141
/// <returns>A Task that can be tracked for completion.</returns>
4242
Task OpenFile(string filePath);
4343

44+
/// <summary>
45+
/// Causes a file to be closed in the editor.
46+
/// </summary>
47+
/// <param name="filePath">The path of the file to be closed.</param>
48+
/// <returns>A Task that can be tracked for completion.</returns>
49+
Task CloseFile(string filePath);
50+
4451
/// <summary>
4552
/// Inserts text into the specified range for the file at the specified path.
4653
/// </summary>

src/PowerShellEditorServices/Nano.PowerShellEditorServices.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
<Compile Include="Session\PowerShellVersionDetails.cs" />
111111
<Compile Include="Session\ProfilePaths.cs" />
112112
<Compile Include="Session\ProgressDetails.cs" />
113+
<Compile Include="Session\RemoteFileManager.cs" />
113114
<Compile Include="Session\RunspaceHandle.cs" />
114115
<Compile Include="Session\SessionPSHost.cs" />
115116
<Compile Include="Session\SessionPSHostRawUserInterface.cs" />

src/PowerShellEditorServices/PowerShellEditorServices.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
<Compile Include="Workspace\FileChange.cs" />
145145
<Compile Include="Workspace\BufferPosition.cs" />
146146
<Compile Include="Workspace\FilePosition.cs" />
147+
<Compile Include="Session\RemoteFileManager.cs" />
147148
<Compile Include="Workspace\ScriptFile.cs" />
148149
<Compile Include="Workspace\ScriptFileMarker.cs" />
149150
<Compile Include="Workspace\ScriptRegion.cs" />

0 commit comments

Comments
 (0)