diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs index fd2ee358d..92d516817 100644 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs +++ b/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs @@ -52,6 +52,18 @@ public static readonly EventType.Create("textDocument/didClose"); } + public class DidSaveTextDocumentNotification + { + public static readonly + EventType Type = + EventType.Create("textDocument/didSave"); + } + + public class DidSaveTextDocumentParams + { + public TextDocumentIdentifier TextDocument { get; set; } + } + public class DidChangeTextDocumentNotification { public static readonly diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index fa5c13a52..56efe1eb2 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -110,6 +110,7 @@ protected override void Initialize() this.SetEventHandler(DidOpenTextDocumentNotification.Type, this.HandleDidOpenTextDocumentNotification); this.SetEventHandler(DidCloseTextDocumentNotification.Type, this.HandleDidCloseTextDocumentNotification); + this.SetEventHandler(DidSaveTextDocumentNotification.Type, this.HandleDidSaveTextDocumentNotification); this.SetEventHandler(DidChangeTextDocumentNotification.Type, this.HandleDidChangeTextDocumentNotification); this.SetEventHandler(DidChangeConfigurationNotification.Type, this.HandleDidChangeConfigurationNotification); @@ -516,6 +517,23 @@ protected async Task HandleDidCloseTextDocumentNotification( Logger.Write(LogLevel.Verbose, "Finished closing document."); } + protected async Task HandleDidSaveTextDocumentNotification( + DidSaveTextDocumentParams saveParams, + EventContext eventContext) + { + ScriptFile savedFile = + this.editorSession.Workspace.GetFile( + saveParams.TextDocument.Uri); + + if (savedFile != null) + { + if (this.editorSession.RemoteFileManager.IsUnderRemoteTempPath(savedFile.FilePath)) + { + await this.editorSession.RemoteFileManager.SaveRemoteFile( + savedFile.FilePath); + } + } + } protected Task HandleDidChangeTextDocumentNotification( DidChangeTextDocumentParams textChangeParams, diff --git a/src/PowerShellEditorServices/Session/RemoteFileManager.cs b/src/PowerShellEditorServices/Session/RemoteFileManager.cs index d60afd00f..b77c0a5e6 100644 --- a/src/PowerShellEditorServices/Session/RemoteFileManager.cs +++ b/src/PowerShellEditorServices/Session/RemoteFileManager.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Text; using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Session @@ -77,6 +78,15 @@ public class RemoteFileManager Get-EventSubscriber -SourceIdentifier PSESRemoteSessionOpenFile -EA Ignore | Remove-Event "; + private const string SetRemoteContentsScript = @" + param( + [string] $RemoteFilePath, + [byte[]] $Content + ) + + Set-Content -Path $RemoteFilePath -Value $Content -Encoding Byte -Force 2>&1 + "; + #endregion #region Constructors @@ -185,6 +195,61 @@ public async Task FetchRemoteFile( return localFilePath; } + /// + /// Saves the contents of the file under the temporary local + /// file cache to its corresponding remote file. + /// + /// + /// The local file whose contents will be saved. It is assumed + /// that the editor has saved the contents of the local cache + /// file to disk before this method is called. + /// + /// A Task to be awaited for completion. + public async Task SaveRemoteFile(string localFilePath) + { + string remoteFilePath = + this.GetMappedPath( + localFilePath, + this.powerShellContext.CurrentRunspace); + + Logger.Write( + LogLevel.Verbose, + $"Saving remote file {remoteFilePath} (local path: {localFilePath})"); + + byte[] localFileContents = null; + try + { + localFileContents = File.ReadAllBytes(localFilePath); + } + catch (IOException e) + { + Logger.WriteException( + "Failed to read contents of local copy of remote file", + e); + + return; + } + + PSCommand saveCommand = new PSCommand(); + saveCommand + .AddScript(SetRemoteContentsScript) + .AddParameter("RemoteFilePath", remoteFilePath) + .AddParameter("Content", localFileContents); + + StringBuilder errorMessages = new StringBuilder(); + + await this.powerShellContext.ExecuteCommand( + saveCommand, + errorMessages, + false, + false); + + if (errorMessages.Length > 0) + { + Logger.Write(LogLevel.Error, $"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); + } + } + /// /// Creates a temporary file with the given name and contents /// corresponding to the specified runspace.