diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 972a17766..9629dab9b 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -51,46 +51,27 @@ Schema is: #> $script:RequiredBuildAssets = @{ $script:ModuleBinPath = @{ - 'PowerShellEditorServices' = @( - 'publish/Serilog.dll', - 'publish/Serilog.Sinks.Async.dll', - 'publish/Serilog.Sinks.Console.dll', - 'publish/Serilog.Sinks.File.dll', - 'publish/Microsoft.Extensions.FileSystemGlobbing.dll', - 'Microsoft.PowerShell.EditorServices.dll', - 'Microsoft.PowerShell.EditorServices.pdb' - ) - - 'PowerShellEditorServices.Host' = @( - 'publish/UnixConsoleEcho.dll', - 'publish/runtimes/osx-64/native/libdisablekeyecho.dylib', - 'publish/runtimes/linux-64/native/libdisablekeyecho.so', - 'publish/Newtonsoft.Json.dll', - 'Microsoft.PowerShell.EditorServices.Host.dll', - 'Microsoft.PowerShell.EditorServices.Host.pdb' - ) - - 'PowerShellEditorServices.Protocol' = @( - 'Microsoft.PowerShell.EditorServices.Protocol.dll', - 'Microsoft.PowerShell.EditorServices.Protocol.pdb' - ) - 'PowerShellEditorServices.Engine' = @( + 'publish/Microsoft.Extensions.DependencyInjection.Abstractions.dll', + 'publish/Microsoft.Extensions.DependencyInjection.dll', + 'publish/Microsoft.Extensions.FileSystemGlobbing.dll', + 'publish/Microsoft.Extensions.Logging.Abstractions.dll', + 'publish/Microsoft.Extensions.Logging.dll', + 'publish/Microsoft.Extensions.Options.dll', + 'publish/Microsoft.Extensions.Primitives.dll', 'publish/Microsoft.PowerShell.EditorServices.Engine.dll', 'publish/Microsoft.PowerShell.EditorServices.Engine.pdb', + 'publish/Newtonsoft.Json.dll', 'publish/OmniSharp.Extensions.JsonRpc.dll', 'publish/OmniSharp.Extensions.LanguageProtocol.dll', 'publish/OmniSharp.Extensions.LanguageServer.dll', + 'publish/runtimes/linux-64/native/libdisablekeyecho.so', + 'publish/runtimes/osx-64/native/libdisablekeyecho.dylib', 'publish/Serilog.dll', 'publish/Serilog.Extensions.Logging.dll', 'publish/Serilog.Sinks.File.dll', - 'publish/Microsoft.Extensions.DependencyInjection.Abstractions.dll', - 'publish/Microsoft.Extensions.DependencyInjection.dll', - 'publish/Microsoft.Extensions.Logging.Abstractions.dll', - 'publish/Microsoft.Extensions.Logging.dll', - 'publish/Microsoft.Extensions.Options.dll', - 'publish/Microsoft.Extensions.Primitives.dll', - 'publish/System.Reactive.dll' + 'publish/System.Reactive.dll', + 'publish/UnixConsoleEcho.dll' ) } diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psm1 b/module/PowerShellEditorServices/PowerShellEditorServices.psm1 index 2d05b73ea..431e208b9 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psm1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psm1 @@ -10,9 +10,6 @@ if ($PSEdition -eq 'Desktop') { Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Desktop/System.Security.Principal.Windows.dll" } -Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.dll" -Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.Host.dll" -Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.Protocol.dll" Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.Engine.dll" function Start-EditorServicesHost { diff --git a/src/PowerShellEditorServices.Engine/Hosting/EditorServicesHost.cs b/src/PowerShellEditorServices.Engine/Hosting/EditorServicesHost.cs index 017f99a60..f67f28c08 100644 --- a/src/PowerShellEditorServices.Engine/Hosting/EditorServicesHost.cs +++ b/src/PowerShellEditorServices.Engine/Hosting/EditorServicesHost.cs @@ -17,6 +17,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Extensions; using Serilog; namespace Microsoft.PowerShell.EditorServices.Engine @@ -233,6 +234,56 @@ public void StartLanguageService( _logger.LogInformation($"LSP NamedPipe: {config.InOutPipeName}\nLSP OutPipe: {config.OutPipeName}"); + var powerShellContext = GetFullyInitializedPowerShellContext(profilePaths); + + _serviceCollection + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(powerShellContext) + .AddSingleton() + .AddSingleton( + (provider) => + { + var extensionService = new ExtensionService( + provider.GetService(), + provider.GetService()); + extensionService.InitializeAsync( + serviceProvider: provider, + editorOperations: provider.GetService()) + .Wait(); + return extensionService; + }) + .AddSingleton( + (provider) => + { + return AnalysisService.Create( + provider.GetService(), + provider.GetService(), + _factory.CreateLogger()); + }); + + _languageServer = new OmnisharpLanguageServerBuilder(_serviceCollection) + { + NamedPipeName = config.InOutPipeName ?? config.InPipeName, + OutNamedPipeName = config.OutPipeName, + LoggerFactory = _factory, + MinimumLogLevel = LogLevel.Trace, + } + .BuildLanguageServer(); + + _logger.LogInformation("Starting language server"); + + Task.Run(_languageServer.StartAsync); + + _logger.LogInformation( + string.Format( + "Language service started, type = {0}, endpoint = {1}", + config.TransportType, config.Endpoint)); + } + + private PowerShellContextService GetFullyInitializedPowerShellContext(ProfilePaths profilePaths) + { var logger = _factory.CreateLogger(); var powerShellContext = new PowerShellContextService( logger, @@ -244,7 +295,7 @@ public void StartLanguageService( // ? (EditorServicesPSHostUserInterface)new TerminalPSHostUserInterface(powerShellContext, logger, _internalHost) // : new ProtocolPSHostUserInterface(powerShellContext, messageSender, logger); EditorServicesPSHostUserInterface hostUserInterface = - (EditorServicesPSHostUserInterface)new TerminalPSHostUserInterface(powerShellContext, logger, _internalHost); + new TerminalPSHostUserInterface(powerShellContext, logger, _internalHost); EditorServicesPSHost psHost = @@ -267,51 +318,17 @@ public void StartLanguageService( foreach (string module in this._additionalModules) { var command = - new System.Management.Automation.PSCommand() + new PSCommand() .AddCommand("Microsoft.PowerShell.Core\\Import-Module") .AddParameter("Name", module); - powerShellContext.ExecuteCommandAsync( + powerShellContext.ExecuteCommandAsync( command, sendOutputToHost: false, sendErrorToHost: true); } - _serviceCollection - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(powerShellContext) - .AddSingleton( - (provider) => { - return AnalysisService.Create( - provider.GetService(), - provider.GetService(), - _factory.CreateLogger()); - } - ); - - _languageServer = new OmnisharpLanguageServerBuilder(_serviceCollection) - { - NamedPipeName = config.InOutPipeName ?? config.InPipeName, - OutNamedPipeName = config.OutPipeName, - LoggerFactory = _factory, - MinimumLogLevel = LogLevel.Trace, - } - .BuildLanguageServer(); - - _logger.LogInformation("Starting language server"); - - Task.Run(_languageServer.StartAsync); - //Task.Factory.StartNew(() => _languageServer.StartAsync(), - // CancellationToken.None, - // TaskCreationOptions.LongRunning, - // TaskScheduler.Default); - - _logger.LogInformation( - string.Format( - "Language service started, type = {0}, endpoint = {1}", - config.TransportType, config.Endpoint)); + return powerShellContext; } /// diff --git a/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs b/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs index ccf221008..3bbf26dff 100644 --- a/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs +++ b/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs @@ -14,6 +14,7 @@ using OmniSharp.Extensions.LanguageServer.Server; using PowerShellEditorServices.Engine.Services.Handlers; using Microsoft.PowerShell.EditorServices.TextDocument; +using System.IO; namespace Microsoft.PowerShell.EditorServices.Engine { @@ -95,7 +96,26 @@ public async Task StartAsync() .WithHandler() .WithHandler() .WithHandler() - .WithHandler(); + .WithHandler() + .WithHandler() + .OnInitialize( + async (languageServer, request) => + { + var serviceProvider = languageServer.Services; + var workspaceService = serviceProvider.GetService(); + + // Grab the workspace path from the parameters + workspaceService.WorkspacePath = request.RootPath; + + // Set the working directory of the PowerShell session to the workspace path + if (workspaceService.WorkspacePath != null + && Directory.Exists(workspaceService.WorkspacePath)) + { + await serviceProvider.GetService().SetWorkingDirectoryAsync( + workspaceService.WorkspacePath, + isPathAlreadyEscaped: false); + } + }); logger.LogInformation("Handlers added"); }); diff --git a/src/PowerShellEditorServices.Engine/PowerShellEditorServices.Engine.csproj b/src/PowerShellEditorServices.Engine/PowerShellEditorServices.Engine.csproj index a7d6b35ca..3c219d986 100644 --- a/src/PowerShellEditorServices.Engine/PowerShellEditorServices.Engine.csproj +++ b/src/PowerShellEditorServices.Engine/PowerShellEditorServices.Engine.csproj @@ -28,14 +28,7 @@ - - - - - - - diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/ComponentRegistry.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/ComponentRegistry.cs deleted file mode 100644 index 9a1de6d01..000000000 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/ComponentRegistry.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Components -{ - /// - /// Provides a default implementation for the IComponentRegistry - /// interface. - /// - public class ComponentRegistry : IComponentRegistry - { - private Dictionary componentRegistry = - new Dictionary(); - - /// - /// Registers an instance of the specified component type - /// or throws an ArgumentException if an instance has - /// already been registered. - /// - /// - /// The component type that the instance represents. - /// - /// - /// The instance of the component to be registered. - /// - /// - /// The provided component instance for convenience in assignment - /// statements. - /// - public object Register(Type componentType, object componentInstance) - { - this.componentRegistry.Add(componentType, componentInstance); - return componentInstance; - } - - - /// - /// Gets the registered instance of the specified - /// component type or throws a KeyNotFoundException if - /// no instance has been registered. - /// - /// - /// The component type for which an instance will be retrieved. - /// - /// The implementation of the specified type. - public object Get(Type componentType) - { - return this.componentRegistry[componentType]; - } - - /// - /// Attempts to retrieve the instance of the specified - /// component type and, if found, stores it in the - /// componentInstance parameter. - /// - /// - /// The out parameter in which the found instance will be stored. - /// - /// - /// The component type for which an instance will be retrieved. - /// - /// - /// True if a registered instance was found, false otherwise. - /// - public bool TryGet(Type componentType, out object componentInstance) - { - componentInstance = null; - - if (this.componentRegistry.TryGetValue(componentType, out componentInstance)) - { - return componentInstance != null; - } - - return false; - } - } -} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/IComponentRegistry.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/IComponentRegistry.cs deleted file mode 100644 index c9f99000f..000000000 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/IComponentRegistry.cs +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; - -namespace Microsoft.PowerShell.EditorServices.Components -{ - /// - /// Specifies the contract for a registry of component interfaces. - /// - public interface IComponentRegistry - { - /// - /// Registers an instance of the specified component type - /// or throws an ArgumentException if an instance has - /// already been registered. - /// - /// - /// The component type that the instance represents. - /// - /// - /// The instance of the component to be registered. - /// - /// - /// The provided component instance for convenience in assignment - /// statements. - /// - object Register( - Type componentType, - object componentInstance); - - /// - /// Gets the registered instance of the specified - /// component type or throws a KeyNotFoundException if - /// no instance has been registered. - /// - /// - /// The component type for which an instance will be retrieved. - /// - /// The implementation of the specified type. - object Get(Type componentType); - - /// - /// Attempts to retrieve the instance of the specified - /// component type and, if found, stores it in the - /// componentInstance parameter. - /// - /// - /// The component type for which an instance will be retrieved. - /// - /// - /// The out parameter in which the found instance will be stored. - /// - /// - /// True if a registered instance was found, false otherwise. - /// - bool TryGet(Type componentType, out object componentInstance); - } -} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/IComponentRegistryExtensions.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/IComponentRegistryExtensions.cs deleted file mode 100644 index 0c6307d5a..000000000 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Components/IComponentRegistryExtensions.cs +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -namespace Microsoft.PowerShell.EditorServices.Components -{ - /// - /// Provides generic helper methods for working with IComponentRegistry - /// methods. - /// - public static class IComponentRegistryExtensions - { - /// - /// Registers an instance of the specified component type - /// or throws an ArgumentException if an instance has - /// already been registered. - /// - /// - /// The IComponentRegistry instance. - /// - /// - /// The instance of the component to be registered. - /// - /// - /// The provided component instance for convenience in assignment - /// statements. - /// - public static TComponent Register( - this IComponentRegistry componentRegistry, - TComponent componentInstance) - where TComponent : class - { - return - (TComponent)componentRegistry.Register( - typeof(TComponent), - componentInstance); - } - - /// - /// Gets the registered instance of the specified - /// component type or throws a KeyNotFoundException if - /// no instance has been registered. - /// - /// - /// The IComponentRegistry instance. - /// - /// The implementation of the specified type. - public static TComponent Get( - this IComponentRegistry componentRegistry) - where TComponent : class - { - return (TComponent)componentRegistry.Get(typeof(TComponent)); - } - - /// - /// Attempts to retrieve the instance of the specified - /// component type and, if found, stores it in the - /// componentInstance parameter. - /// - /// - /// The IComponentRegistry instance. - /// - /// - /// The out parameter in which the found instance will be stored. - /// - /// - /// True if a registered instance was found, false otherwise. - /// - public static bool TryGet( - this IComponentRegistry componentRegistry, - out TComponent componentInstance) - where TComponent : class - { - object componentObject = null; - componentInstance = null; - - if (componentRegistry.TryGet(typeof(TComponent), out componentObject)) - { - componentInstance = componentObject as TComponent; - return componentInstance != null; - } - - return false; - } - } -} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/EditorOperationsService.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/EditorOperationsService.cs new file mode 100644 index 000000000..6d651acde --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/EditorOperationsService.cs @@ -0,0 +1,182 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using PowerShellEditorServices.Engine.Services.Handlers; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Extensions +{ + internal class EditorOperationsService : IEditorOperations + { + private const bool DefaultPreviewSetting = true; + + private WorkspaceService _workspaceService; + private ILanguageServer _languageServer; + + public EditorOperationsService( + WorkspaceService workspaceService, + ILanguageServer languageServer) + { + this._workspaceService = workspaceService; + this._languageServer = languageServer; + } + + public async Task GetEditorContextAsync() + { + ClientEditorContext clientContext = + await _languageServer.SendRequest( + "editor/getEditorContext", + new GetEditorContextRequest()); + + return this.ConvertClientEditorContext(clientContext); + } + + public async Task InsertTextAsync(string filePath, string text, BufferRange insertRange) + { + await _languageServer.SendRequest("editor/insertText", new InsertTextRequest + { + FilePath = filePath, + InsertText = text, + InsertRange = + new Range + { + Start = new Position + { + Line = insertRange.Start.Line - 1, + Character = insertRange.Start.Column - 1 + }, + End = new Position + { + Line = insertRange.End.Line - 1, + Character = insertRange.End.Column - 1 + } + } + }); + } + + public async Task SetSelectionAsync(BufferRange selectionRange) + { + + await _languageServer.SendRequest("editor/setSelection", new SetSelectionRequest + { + SelectionRange = + new Range + { + Start = new Position + { + Line = selectionRange.Start.Line - 1, + Character = selectionRange.Start.Column - 1 + }, + End = new Position + { + Line = selectionRange.End.Line - 1, + Character = selectionRange.End.Column - 1 + } + } + }); + } + + public EditorContext ConvertClientEditorContext( + ClientEditorContext clientContext) + { + ScriptFile scriptFile = _workspaceService.CreateScriptFileFromFileBuffer( + clientContext.CurrentFilePath, + clientContext.CurrentFileContent); + + return + new EditorContext( + this, + scriptFile, + new BufferPosition( + (int) clientContext.CursorPosition.Line + 1, + (int) clientContext.CursorPosition.Character + 1), + new BufferRange( + (int) clientContext.SelectionRange.Start.Line + 1, + (int) clientContext.SelectionRange.Start.Character + 1, + (int) clientContext.SelectionRange.End.Line + 1, + (int) clientContext.SelectionRange.End.Character + 1), + clientContext.CurrentFileLanguage); + } + + public async Task NewFileAsync() + { + await _languageServer.SendRequest("editor/newFile", null); + } + + public async Task OpenFileAsync(string filePath) + { + await _languageServer.SendRequest("editor/openFile", new OpenFileDetails + { + FilePath = filePath, + Preview = DefaultPreviewSetting + }); + } + + public async Task OpenFileAsync(string filePath, bool preview) + { + await _languageServer.SendRequest("editor/openFile", new OpenFileDetails + { + FilePath = filePath, + Preview = preview + }); + } + + public async Task CloseFileAsync(string filePath) + { + await _languageServer.SendRequest("editor/closeFile", filePath); + } + + public async Task SaveFileAsync(string filePath) + { + await SaveFileAsync(filePath, null); + } + + public async Task SaveFileAsync(string currentPath, string newSavePath) + { + await _languageServer.SendRequest("editor/saveFile", new SaveFileDetails + { + FilePath = currentPath, + NewPath = newSavePath + }); + } + + public string GetWorkspacePath() + { + return _workspaceService.WorkspacePath; + } + + public string GetWorkspaceRelativePath(string filePath) + { + return _workspaceService.GetRelativePath(filePath); + } + + public async Task ShowInformationMessageAsync(string message) + { + await _languageServer.SendRequest("editor/showInformationMessage", message); + } + + public async Task ShowErrorMessageAsync(string message) + { + await _languageServer.SendRequest("editor/showErrorMessage", message); + } + + public async Task ShowWarningMessageAsync(string message) + { + await _languageServer.SendRequest("editor/showWarningMessage", message); + } + + public async Task SetStatusBarMessageAsync(string message, int? timeout) + { + await _languageServer.SendRequest("editor/setStatusBarMessage", new StatusBarMessageDetails + { + Message = message, + Timeout = timeout + }); + } + } +} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/ExtensionService.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/ExtensionService.cs similarity index 80% rename from src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/ExtensionService.cs rename to src/PowerShellEditorServices.Engine/Services/PowerShellContext/ExtensionService.cs index b96ac9e12..8ecc19300 100644 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/ExtensionService.cs +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/ExtensionService.cs @@ -3,7 +3,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Components; +using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; using System.Collections.Generic; using System.Management.Automation; @@ -22,6 +23,8 @@ public class ExtensionService private Dictionary editorCommands = new Dictionary(); + private readonly ILanguageServer _languageServer; + #endregion #region Properties @@ -52,9 +55,10 @@ public class ExtensionService /// PowerShellContext for loading and executing extension code. /// /// A PowerShellContext used to execute extension code. - public ExtensionService(PowerShellContextService powerShellContext) + public ExtensionService(PowerShellContextService powerShellContext, ILanguageServer languageServer) { this.PowerShellContext = powerShellContext; + _languageServer = languageServer; } #endregion @@ -66,17 +70,21 @@ public ExtensionService(PowerShellContextService powerShellContext) /// implementation for future interaction with the host editor. /// /// An IEditorOperations implementation. - /// An IComponentRegistry instance which provides components in the session. /// A Task that can be awaited for completion. public async Task InitializeAsync( - IEditorOperations editorOperations, - IComponentRegistry componentRegistry) + IServiceProvider serviceProvider, + IEditorOperations editorOperations) { + // Attach to ExtensionService events + this.CommandAdded += ExtensionService_ExtensionAddedAsync; + this.CommandUpdated += ExtensionService_ExtensionUpdatedAsync; + this.CommandRemoved += ExtensionService_ExtensionRemovedAsync; + this.EditorObject = new EditorObject( + serviceProvider, this, - editorOperations, - componentRegistry); + editorOperations); // Register the editor object in the runspace PSCommand variableCommand = new PSCommand(); @@ -177,6 +185,7 @@ public EditorCommand[] GetCommands() this.editorCommands.Values.CopyTo(commands,0); return commands; } + #endregion #region Events @@ -211,6 +220,34 @@ private void OnCommandRemoved(EditorCommand command) this.CommandRemoved?.Invoke(this, command); } + private void ExtensionService_ExtensionAddedAsync(object sender, EditorCommand e) + { + _languageServer.SendNotification("powerShell/extensionCommandAdded", + new ExtensionCommandAddedNotification + { + Name = e.Name, + DisplayName = e.DisplayName + }); + } + + private void ExtensionService_ExtensionUpdatedAsync(object sender, EditorCommand e) + { + _languageServer.SendNotification("powerShell/extensionCommandUpdated", + new ExtensionCommandUpdatedNotification + { + Name = e.Name, + }); + } + + private void ExtensionService_ExtensionRemovedAsync(object sender, EditorCommand e) + { + _languageServer.SendNotification("powerShell/extensionCommandRemoved", + new ExtensionCommandRemovedNotification + { + Name = e.Name, + }); + } + #endregion } } diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs index 39da959cf..44a72a6c8 100644 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs @@ -3,7 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Components; using System; using System.Reflection; @@ -17,8 +16,9 @@ public class EditorObject { #region Private Fields - private ExtensionService extensionService; - private IEditorOperations editorOperations; + private readonly IServiceProvider _serviceProvider; + private readonly ExtensionService _extensionService; + private readonly IEditorOperations _editorOperations; #endregion @@ -42,12 +42,6 @@ public Version EditorServicesVersion /// public EditorWindow Window { get; private set; } - /// - /// Gets the component registry for the session. - /// - /// - public IComponentRegistry Components { get; private set; } - #endregion /// @@ -55,19 +49,18 @@ public Version EditorServicesVersion /// /// An ExtensionService which handles command registration. /// An IEditorOperations implementation which handles operations in the host editor. - /// An IComponentRegistry instance which provides components in the session. public EditorObject( + IServiceProvider serviceProvider, ExtensionService extensionService, - IEditorOperations editorOperations, - IComponentRegistry componentRegistry) + IEditorOperations editorOperations) { - this.extensionService = extensionService; - this.editorOperations = editorOperations; - this.Components = componentRegistry; + this._serviceProvider = serviceProvider; + this._extensionService = extensionService; + this._editorOperations = editorOperations; // Create API area objects - this.Workspace = new EditorWorkspace(this.editorOperations); - this.Window = new EditorWindow(this.editorOperations); + this.Workspace = new EditorWorkspace(this._editorOperations); + this.Window = new EditorWindow(this._editorOperations); } /// @@ -77,7 +70,7 @@ public EditorObject( /// True if the command is newly registered, false if the command already exists. public bool RegisterCommand(EditorCommand editorCommand) { - return this.extensionService.RegisterCommand(editorCommand); + return this._extensionService.RegisterCommand(editorCommand); } /// @@ -86,7 +79,7 @@ public bool RegisterCommand(EditorCommand editorCommand) /// The name of the command to be unregistered. public void UnregisterCommand(string commandName) { - this.extensionService.UnregisterCommand(commandName); + this._extensionService.UnregisterCommand(commandName); } /// @@ -95,7 +88,7 @@ public void UnregisterCommand(string commandName) /// An Array of all registered EditorCommands. public EditorCommand[] GetCommands() { - return this.extensionService.GetCommands(); + return this._extensionService.GetCommands(); } /// /// Gets the EditorContext which contains the state of the editor @@ -104,7 +97,16 @@ public EditorCommand[] GetCommands() /// A instance of the EditorContext class. public EditorContext GetEditorContext() { - return this.editorOperations.GetEditorContextAsync().Result; + return this._editorOperations.GetEditorContextAsync().Result; + } + + /// + /// Get's the desired service which allows for advanced control of PowerShellEditorServices. + /// + /// The singleton service object of the type requested. + public object GetService(Type type) + { + return _serviceProvider.GetService(type); } } } diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorRequests.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorRequests.cs new file mode 100644 index 000000000..38e5c80f8 --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorRequests.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer +{ + public class ExtensionCommandAddedNotification + { + public string Name { get; set; } + + public string DisplayName { get; set; } + } + + public class ExtensionCommandUpdatedNotification + { + public string Name { get; set; } + } + + public class ExtensionCommandRemovedNotification + { + public string Name { get; set; } + } + + + public class GetEditorContextRequest + {} + + public enum EditorCommandResponse + { + Unsupported, + OK + } + + public class InsertTextRequest + { + public string FilePath { get; set; } + + public string InsertText { get; set; } + + public Range InsertRange { get; set; } + } + + public class SetSelectionRequest + { + public Range SelectionRange { get; set; } + } + + public class SetCursorPositionRequest + { + public Position CursorPosition { get; set; } + } + + public class OpenFileDetails + { + public string FilePath { get; set; } + + public bool Preview { get; set; } + } + + public class SaveFileDetails + { + public string FilePath { get; set; } + + public string NewPath { get; set; } + } + + public class StatusBarMessageDetails + { + public string Message { get; set; } + + public int? Timeout { get; set; } + } +} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs new file mode 100644 index 000000000..e44825c95 --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs @@ -0,0 +1,30 @@ +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace PowerShellEditorServices.Engine.Services.Handlers +{ + [Serial, Method("powerShell/invokeExtensionCommand")] + public interface IInvokeExtensionCommandHandler : IJsonRpcNotificationHandler { } + + public class InvokeExtensionCommandParams : IRequest + { + public string Name { get; set; } + + public ClientEditorContext Context { get; set; } + } + + public class ClientEditorContext + { + public string CurrentFileContent { get; set; } + + public string CurrentFileLanguage { get; set; } + + public string CurrentFilePath { get; set; } + + public Position CursorPosition { get; set; } + + public Range SelectionRange { get; set; } + + } +} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs new file mode 100644 index 000000000..fee04ff84 --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs @@ -0,0 +1,40 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Extensions; +using OmniSharp.Extensions.Embedded.MediatR; + +namespace PowerShellEditorServices.Engine.Services.Handlers +{ + internal class InvokeExtensionCommandHandler : IInvokeExtensionCommandHandler + { + private readonly ILogger _logger; + private readonly ExtensionService _extensionService; + private readonly EditorOperationsService _editorOperationsService; + + public InvokeExtensionCommandHandler( + ILoggerFactory factory, + ExtensionService extensionService, + EditorOperationsService editorOperationsService + ) + { + _logger = factory.CreateLogger(); + _extensionService = extensionService; + _editorOperationsService = editorOperationsService; + } + + public async Task Handle(InvokeExtensionCommandParams request, CancellationToken cancellationToken) + { + // We can now await here because we handle asynchronous message handling. + EditorContext editorContext = + _editorOperationsService.ConvertClientEditorContext( + request.Context); + + await _extensionService.InvokeCommandAsync( + request.Name, + editorContext); + + return await Unit.Task; + } + } +} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs index b24e2c7aa..52b8b3411 100644 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -1,19 +1,23 @@ using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices; namespace PowerShellEditorServices.Engine.Services.Handlers { public class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler { private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; - public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory) + public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, PowerShellContextService powerShellContextService) { _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; } public Task Handle(GetPSHostProcesssesParams request, CancellationToken cancellationToken) @@ -51,11 +55,12 @@ public Task Handle(GetPSHostProcesssesParams request, C return Task.FromResult(psHostProcesses.ToArray()); } - public Task Handle(GetRunspaceParams request, CancellationToken cancellationToken) + public async Task Handle(GetRunspaceParams request, CancellationToken cancellationToken) { IEnumerable runspaces = null; - if (request.ProcessId == null) { + if (request.ProcessId == null) + { request.ProcessId = "current"; } @@ -64,8 +69,8 @@ public Task Handle(GetRunspaceParams request, CancellationTo if (int.TryParse(request.ProcessId, out int pid)) { // Create a remote runspace that we will invoke Get-Runspace in. - using(var rs = RunspaceFactory.CreateRunspace(new NamedPipeConnectionInfo(pid))) - using(var ps = PowerShell.Create()) + using (var rs = RunspaceFactory.CreateRunspace(new NamedPipeConnectionInfo(pid))) + using (var ps = PowerShell.Create()) { rs.Open(); ps.Runspace = rs; @@ -75,11 +80,10 @@ public Task Handle(GetRunspaceParams request, CancellationTo } else { - // TODO: Bring back - // var psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace"); - // var sb = new StringBuilder(); - // // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later. - // runspaces = await editorSession.PowerShellContext.ExecuteCommandAsync(psCommand, sb); + var psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace"); + var sb = new StringBuilder(); + // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later. + runspaces = await _powerShellContextService.ExecuteCommandAsync(psCommand, sb); } var runspaceResponses = new List(); @@ -98,7 +102,7 @@ public Task Handle(GetRunspaceParams request, CancellationTo } } - return Task.FromResult(runspaceResponses.ToArray()); + return runspaceResponses.ToArray(); } } } diff --git a/src/PowerShellEditorServices.Engine/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices.Engine/Services/Symbols/SymbolsService.cs index 92508adb2..468bfb62e 100644 --- a/src/PowerShellEditorServices.Engine/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices.Engine/Services/Symbols/SymbolsService.cs @@ -118,10 +118,6 @@ public List FindReferencesOfSymbol( return null; } - int symbolOffset = referencedFiles[0].GetOffsetAtPosition( - foundSymbol.ScriptRegion.StartLineNumber, - foundSymbol.ScriptRegion.StartColumnNumber); - // NOTE: we use to make sure aliases were loaded but took it out because we needed the pipeline thread. // We want to look for references first in referenced files, hence we use ordered dictionary diff --git a/src/PowerShellEditorServices.Engine/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices.Engine/Services/Workspace/Handlers/ConfigurationHandler.cs index 652bb9ce4..c22ea8d90 100644 --- a/src/PowerShellEditorServices.Engine/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices.Engine/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -47,7 +47,7 @@ public async Task Handle(DidChangeConfigurationParams request, Cancellatio { return await Unit.Task; } - // TODO ADD THIS BACK IN + bool oldLoadProfiles = _configurationService.CurrentSettings.EnableProfileLoading; bool oldScriptAnalysisEnabled = _configurationService.CurrentSettings.ScriptAnalysis.Enable ?? false; diff --git a/test/Pester/EditorServices.Integration.Tests.ps1 b/test/Pester/EditorServices.Integration.Tests.ps1 index edc53f684..1054c8268 100644 --- a/test/Pester/EditorServices.Integration.Tests.ps1 +++ b/test/Pester/EditorServices.Integration.Tests.ps1 @@ -98,7 +98,8 @@ Describe "Loading and running PowerShellEditorServices" { # This test MUST be first It "Starts and responds to an initialization request" { - $request = Send-LspInitializeRequest -Client $client + $startDir = New-Item -ItemType Directory TestDrive:\start + $request = Send-LspInitializeRequest -Client $client -RootPath ($startDir.FullName) $response = Get-LspResponse -Client $client -Id $request.Id #-WaitMillis 99999 $response.Id | Should -BeExactly $request.Id @@ -301,7 +302,6 @@ Get-Bar -Uri ([Uri]::new($filePath).AbsoluteUri) ` -LineNumber 5 ` -CharacterNumber 0 - $response = Get-LspResponse -Client $client -Id $request.Id $response.Result.Count | Should -BeExactly 2 diff --git a/tools/PsesPsClient/PsesPsClient.psd1 b/tools/PsesPsClient/PsesPsClient.psd1 index 34c3921fe..2d1f65aaa 100644 --- a/tools/PsesPsClient/PsesPsClient.psd1 +++ b/tools/PsesPsClient/PsesPsClient.psd1 @@ -54,7 +54,10 @@ PowerShellVersion = '5.1' # RequiredModules = @() # Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() +RequiredAssemblies = @( + 'Microsoft.PowerShell.EditorServices.dll' + 'Microsoft.PowerShell.EditorServices.Protocol.dll' +) # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @() diff --git a/tools/PsesPsClient/PsesPsClient.psm1 b/tools/PsesPsClient/PsesPsClient.psm1 index 099007ad9..132e912a5 100644 --- a/tools/PsesPsClient/PsesPsClient.psm1 +++ b/tools/PsesPsClient/PsesPsClient.psm1 @@ -681,7 +681,7 @@ function Get-LspResponse [Parameter()] [int] - $WaitMillis = 5000 + $WaitMillis = 10000 ) $lspResponse = $null diff --git a/tools/PsesPsClient/build.ps1 b/tools/PsesPsClient/build.ps1 index a9da9a778..82874ab3e 100644 --- a/tools/PsesPsClient/build.ps1 +++ b/tools/PsesPsClient/build.ps1 @@ -19,6 +19,8 @@ $script:ModuleComponents = @{ "bin/Debug/netstandard2.0/publish/Newtonsoft.Json.dll" = "Newtonsoft.Json.dll" "PsesPsClient.psm1" = "PsesPsClient.psm1" "PsesPsClient.psd1" = "PsesPsClient.psd1" + "bin/Debug/netstandard2.0/Microsoft.PowerShell.EditorServices.Protocol.dll" = "Microsoft.PowerShell.EditorServices.Protocol.dll" + "bin/Debug/netstandard2.0/Microsoft.PowerShell.EditorServices.dll" = "Microsoft.PowerShell.EditorServices.dll" } $binDir = "$PSScriptRoot/bin"