diff --git a/.gitignore b/.gitignore index 3567788f8..be64ce05c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ docs/metadata/ *.zip # Generated build info file -src/PowerShellEditorServices.Host/BuildInfo/BuildInfo.cs +src/PowerShellEditorServices.Engine/Hosting/BuildInfo.cs # quickbuild.exe /VersionGeneratingLogs/ @@ -67,6 +67,7 @@ PowerShellEditorServices.sln.ide/storage.ide # Don't include PlatyPS generated MAML module/PowerShellEditorServices/Commands/en-US/*-help.xml +module/PowerShellEditorServices.VSCode/en-US/*-help.xml # Don't include Third Party Notices in module folder module/PowerShellEditorServices/Third\ Party\ Notices.txt diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 0ac9ff998..9c4f77faa 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -27,7 +27,7 @@ $script:ModuleBinPath = "$PSScriptRoot/module/PowerShellEditorServices/bin/" $script:VSCodeModuleBinPath = "$PSScriptRoot/module/PowerShellEditorServices.VSCode/bin/" $script:WindowsPowerShellFrameworkTarget = 'net461' $script:NetFrameworkPlatformId = 'win' -$script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerShellEditorServices.Host", "BuildInfo", "BuildInfo.cs") +$script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerShellEditorServices.Engine", "Hosting", "BuildInfo.cs") $script:PSCoreModulePath = $null @@ -174,7 +174,7 @@ function Invoke-WithCreateDefaultHook { } } -task SetupDotNet -Before Clean, Build, TestHost, TestServer, TestProtocol, TestE2E, PackageNuGet { +task SetupDotNet -Before Clean, Build, TestHost, TestServer, TestProtocol, TestE2E { $dotnetPath = "$PSScriptRoot/.dotnet" $dotnetExePath = if ($script:IsUnix) { "$dotnetPath/dotnet" } else { "$dotnetPath/dotnet.exe" } @@ -253,7 +253,7 @@ task Clean { Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-Item -Force -ErrorAction Ignore } -task GetProductVersion -Before PackageNuGet, PackageModule, UploadArtifacts { +task GetProductVersion -Before PackageModule, UploadArtifacts { [xml]$props = Get-Content .\PowerShellEditorServices.Common.props $script:BuildNumber = 9999 @@ -304,7 +304,7 @@ task CreateBuildInfo -Before Build { [string]$buildTime = [datetime]::Now.ToString("s", [System.Globalization.CultureInfo]::InvariantCulture) $buildInfoContents = @" -namespace Microsoft.PowerShell.EditorServices.Host +namespace Microsoft.PowerShell.EditorServices.Engine.Hosting { public static class BuildInfo { @@ -319,9 +319,7 @@ namespace Microsoft.PowerShell.EditorServices.Host } task Build { - exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:TargetPlatform } exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices.Engine\PowerShellEditorServices.Engine.csproj -f $script:TargetPlatform } - exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj -f $script:TargetPlatform } exec { & $script:dotnetExe build -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj $script:TargetFrameworksParam } } @@ -478,12 +476,7 @@ task RestorePsesModules -After Build { task BuildCmdletHelp { New-ExternalHelp -Path $PSScriptRoot\module\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US -Force -} - -task PackageNuGet { - exec { & $script:dotnetExe pack -c $Configuration --version-suffix $script:VersionSuffix .\src\PowerShellEditorServices\PowerShellEditorServices.csproj $script:TargetFrameworksParam } - exec { & $script:dotnetExe pack -c $Configuration --version-suffix $script:VersionSuffix .\src\PowerShellEditorServices.Protocol\PowerShellEditorServices.Protocol.csproj $script:TargetFrameworksParam } - exec { & $script:dotnetExe pack -c $Configuration --version-suffix $script:VersionSuffix .\src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj $script:TargetFrameworksParam } + New-ExternalHelp -Path $PSScriptRoot\module\PowerShellEditorServices.VSCode\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices.VSCode\en-US -Force } task PackageModule { @@ -499,4 +492,4 @@ task UploadArtifacts -If ($null -ne $env:TF_BUILD) { } # The default task is to run the entire CI build -task . GetProductVersion, Clean, Build, Test, BuildCmdletHelp, PackageNuGet, PackageModule, UploadArtifacts +task . GetProductVersion, Clean, Build, Test, BuildCmdletHelp, PackageModule, UploadArtifacts diff --git a/PowerShellEditorServices.sln b/PowerShellEditorServices.sln index 71e28ed0d..91156418d 100644 --- a/PowerShellEditorServices.sln +++ b/PowerShellEditorServices.sln @@ -7,10 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F594E7FD-1E7 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{422E561A-8118-4BE7-A54F-9309E4F03AAE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices", "src\PowerShellEditorServices\PowerShellEditorServices.csproj", "{81E8CBCD-6319-49E7-9662-0475BD0791F4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices.Host", "src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj", "{B2F6369A-D737-4AFD-8B81-9B094DB07DA7}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices.Test.Host", "test\PowerShellEditorServices.Test.Host\PowerShellEditorServices.Test.Host.csproj", "{3A5DDD20-5BD0-42F4-89F4-ACC0CE554028}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices.Test", "test\PowerShellEditorServices.Test\PowerShellEditorServices.Test.csproj", "{8ED116F4-9DDF-4C49-AB96-AE462E3D64C3}" @@ -22,8 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{E231 scripts\AddCopyrightHeaders.ps1 = scripts\AddCopyrightHeaders.ps1 EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices.Protocol", "src\PowerShellEditorServices.Protocol\PowerShellEditorServices.Protocol.csproj", "{F8A0946A-5D25-4651-8079-B8D5776916FB}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices.Test.Protocol", "test\PowerShellEditorServices.Test.Protocol\PowerShellEditorServices.Test.Protocol.csproj", "{E3A5CF5D-6E41-44AC-AE0A-4C227E4BACD4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellEditorServices.VSCode", "src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj", "{3B38E8DA-8BFF-4264-AF16-47929E6398A3}" @@ -42,30 +36,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Debug|x64.ActiveCfg = Debug|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Debug|x64.Build.0 = Debug|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Debug|x86.ActiveCfg = Debug|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Debug|x86.Build.0 = Debug|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Release|Any CPU.Build.0 = Release|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Release|x64.ActiveCfg = Release|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Release|x64.Build.0 = Release|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Release|x86.ActiveCfg = Release|Any CPU - {81E8CBCD-6319-49E7-9662-0475BD0791F4}.Release|x86.Build.0 = Release|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Debug|x64.ActiveCfg = Debug|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Debug|x64.Build.0 = Debug|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Debug|x86.ActiveCfg = Debug|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Debug|x86.Build.0 = Debug|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Release|Any CPU.Build.0 = Release|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Release|x64.ActiveCfg = Release|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Release|x64.Build.0 = Release|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Release|x86.ActiveCfg = Release|Any CPU - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7}.Release|x86.Build.0 = Release|Any CPU {3A5DDD20-5BD0-42F4-89F4-ACC0CE554028}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3A5DDD20-5BD0-42F4-89F4-ACC0CE554028}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A5DDD20-5BD0-42F4-89F4-ACC0CE554028}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -102,18 +72,6 @@ Global {6A20B9E9-DE66-456E-B4F5-ACFD1A95C3CA}.Release|x64.Build.0 = Release|Any CPU {6A20B9E9-DE66-456E-B4F5-ACFD1A95C3CA}.Release|x86.ActiveCfg = Release|Any CPU {6A20B9E9-DE66-456E-B4F5-ACFD1A95C3CA}.Release|x86.Build.0 = Release|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Debug|x64.ActiveCfg = Debug|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Debug|x64.Build.0 = Debug|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Debug|x86.ActiveCfg = Debug|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Debug|x86.Build.0 = Debug|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Release|Any CPU.Build.0 = Release|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Release|x64.ActiveCfg = Release|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Release|x64.Build.0 = Release|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Release|x86.ActiveCfg = Release|Any CPU - {F8A0946A-5D25-4651-8079-B8D5776916FB}.Release|x86.Build.0 = Release|Any CPU {E3A5CF5D-6E41-44AC-AE0A-4C227E4BACD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E3A5CF5D-6E41-44AC-AE0A-4C227E4BACD4}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3A5CF5D-6E41-44AC-AE0A-4C227E4BACD4}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -167,12 +125,9 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {81E8CBCD-6319-49E7-9662-0475BD0791F4} = {F594E7FD-1E72-4E51-A496-B019C2BA3180} - {B2F6369A-D737-4AFD-8B81-9B094DB07DA7} = {F594E7FD-1E72-4E51-A496-B019C2BA3180} {3A5DDD20-5BD0-42F4-89F4-ACC0CE554028} = {422E561A-8118-4BE7-A54F-9309E4F03AAE} {8ED116F4-9DDF-4C49-AB96-AE462E3D64C3} = {422E561A-8118-4BE7-A54F-9309E4F03AAE} {6A20B9E9-DE66-456E-B4F5-ACFD1A95C3CA} = {422E561A-8118-4BE7-A54F-9309E4F03AAE} - {F8A0946A-5D25-4651-8079-B8D5776916FB} = {F594E7FD-1E72-4E51-A496-B019C2BA3180} {E3A5CF5D-6E41-44AC-AE0A-4C227E4BACD4} = {422E561A-8118-4BE7-A54F-9309E4F03AAE} {3B38E8DA-8BFF-4264-AF16-47929E6398A3} = {F594E7FD-1E72-4E51-A496-B019C2BA3180} {29EEDF03-0990-45F4-846E-2616970D1FA2} = {F594E7FD-1E72-4E51-A496-B019C2BA3180} diff --git a/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psd1 b/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psd1 index ddb9aa389..09ff913ff 100644 --- a/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psd1 +++ b/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psd1 @@ -9,7 +9,7 @@ @{ # Script module or binary module file associated with this manifest. -RootModule = 'PowerShellEditorServices.VSCode.psm1' +RootModule = "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.VSCode.dll" # Version number of this module. ModuleVersion = '0.2.0' @@ -69,14 +69,14 @@ Description = 'Provides added functionality to PowerShell Editor Services for th # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = @('New-VSCodeHtmlContentView', - 'Show-VSCodeHtmlContentView', - 'Close-VSCodeHtmlContentView', - 'Set-VSCodeHtmlContentView', - 'Write-VSCodeHtmlContentView') +FunctionsToExport = @() # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() +CmdletsToExport = @('New-VSCodeHtmlContentView', + 'Show-VSCodeHtmlContentView', + 'Close-VSCodeHtmlContentView', + 'Set-VSCodeHtmlContentView', + 'Write-VSCodeHtmlContentView') # Variables to export from this module VariablesToExport = '*' diff --git a/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psm1 b/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psm1 deleted file mode 100644 index 12b205c98..000000000 --- a/module/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.psm1 +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.VSCode.dll" - -if ($psEditor -is [Microsoft.PowerShell.EditorServices.Engine.Services.PowerShellContext.EditorObject]) { - [Microsoft.PowerShell.EditorServices.VSCode.ComponentRegistration]::Register($psEditor.Components) -} -else { - Write-Verbose '$psEditor object not found in the session, components will not be registered.' -} - -Microsoft.PowerShell.Management\Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -Recurse | ForEach-Object { - . $PSItem.FullName -} diff --git a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Close-VSCodeHtmlContentView.ps1 b/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Close-VSCodeHtmlContentView.ps1 deleted file mode 100644 index f8cf0c9c6..000000000 --- a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Close-VSCodeHtmlContentView.ps1 +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -function Close-VSCodeHtmlContentView { - <# - .SYNOPSIS - Closes an HtmlContentView. - - .DESCRIPTION - Closes an HtmlContentView inside of Visual Studio Code if - it is displayed. - - .PARAMETER HtmlContentView - The HtmlContentView to be closed. - - .EXAMPLE - Close-VSCodeHtmlContentView -HtmlContentView $htmlContentView - #> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [Alias("View")] - [ValidateNotNull()] - [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentView] - $HtmlContentView - ) - - process { - $HtmlContentView.Close().Wait(); - } -} diff --git a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/New-VSCodeHtmlContentView.ps1 b/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/New-VSCodeHtmlContentView.ps1 deleted file mode 100644 index d76c69fd4..000000000 --- a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/New-VSCodeHtmlContentView.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -function New-VSCodeHtmlContentView { - <# - .SYNOPSIS - Creates a custom view in Visual Studio Code which displays HTML content. - - .DESCRIPTION - Creates a custom view in Visual Studio Code which displays HTML content. - - .PARAMETER Title - The title of the view. - - .PARAMETER ShowInColumn - If specified, causes the new view to be displayed in the specified column. - If unspecified, the Show-VSCodeHtmlContentView cmdlet will need to be used - to display the view. - - .EXAMPLE - # Create a new view called "My Custom View" - $htmlContentView = New-VSCodeHtmlContentView -Title "My Custom View" - - .EXAMPLE - # Create a new view and show it in the second view column - $htmlContentView = New-VSCodeHtmlContentView -Title "My Custom View" -ShowInColumn Two - #> - [CmdletBinding()] - [OutputType([Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentView])] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $Title, - - [Parameter(Mandatory = $false)] - [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.ViewColumn] - $ShowInColumn - ) - - process { - if ($psEditor -is [Microsoft.PowerShell.EditorServices.Engine.Services.PowerShellContext.EditorObject]) { - $viewFeature = $psEditor.Components.Get([Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentViews]) - $view = $viewFeature.CreateHtmlContentViewAsync($Title).Result - - if ($ShowInColumn) { - $view.Show($ShowInColumn).Wait(); - } - - return $view - } - } -} diff --git a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Set-VSCodeHtmlContentView.ps1 b/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Set-VSCodeHtmlContentView.ps1 deleted file mode 100644 index 98abf16a4..000000000 --- a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Set-VSCodeHtmlContentView.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -function Set-VSCodeHtmlContentView { - <# - .SYNOPSIS - Sets the content of an HtmlContentView. - - .DESCRIPTION - Sets the content of an HtmlContentView. If an empty string - is passed, it causes the view's content to be cleared. - - .PARAMETER HtmlContentView - The HtmlContentView where content will be set. - - .PARAMETER HtmlBodyContent - The HTML content that will be placed inside the tag - of the view. - - .PARAMETER JavaScriptPaths - An array of paths to JavaScript files that will be loaded - into the view. - - .PARAMETER StyleSheetPaths - An array of paths to stylesheet (CSS) files that will be - loaded into the view. - - .EXAMPLE - # Set the view content with an h1 header - Set-VSCodeHtmlContentView -HtmlContentView $htmlContentView -HtmlBodyContent "

Hello world!

" - - .EXAMPLE - # Clear the view - Set-VSCodeHtmlContentView -View $htmlContentView -Content "" - #> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [Alias("View")] - [ValidateNotNull()] - [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentView] - $HtmlContentView, - - [Parameter(Mandatory = $true)] - [Alias("Content")] - [AllowEmptyString()] - [string] - $HtmlBodyContent, - - [Parameter(Mandatory = $false)] - [string[]] - $JavaScriptPaths, - - [Parameter(Mandatory = $false)] - [string[]] - $StyleSheetPaths - ) - - process { - $htmlContent = New-Object Microsoft.PowerShell.EditorServices.VSCode.CustomViews.HtmlContent - $htmlContent.BodyContent = $HtmlBodyContent - $htmlContent.JavaScriptPaths = $JavaScriptPaths - $htmlContent.StyleSheetPaths = $StyleSheetPaths - - $HtmlContentView.SetContentAsync($htmlContent).Wait(); - } -} diff --git a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Show-VSCodeHtmlContentView.ps1 b/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Show-VSCodeHtmlContentView.ps1 deleted file mode 100644 index 1be803471..000000000 --- a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Show-VSCodeHtmlContentView.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -function Show-VSCodeHtmlContentView { - <# - .SYNOPSIS - Shows an HtmlContentView. - - .DESCRIPTION - Shows an HtmlContentView that has been created and not shown - yet or has previously been closed. - - .PARAMETER HtmlContentView - The HtmlContentView that will be shown. - - .PARAMETER ViewColumn - If specified, causes the new view to be displayed in the specified column. - - .EXAMPLE - # Shows the view in the first editor column - Show-VSCodeHtmlContentView -HtmlContentView $htmlContentView - - .EXAMPLE - # Shows the view in the third editor column - Show-VSCodeHtmlContentView -View $htmlContentView -Column Three - #> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [Alias("View")] - [ValidateNotNull()] - [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentView] - $HtmlContentView, - - [Parameter(Mandatory = $false)] - [Alias("Column")] - [ValidateNotNull()] - [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.ViewColumn] - $ViewColumn = [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.ViewColumn]::One - ) - - process { - $HtmlContentView.Show($ViewColumn).Wait() - } -} diff --git a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Write-VSCodeHtmlContentView.ps1 b/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Write-VSCodeHtmlContentView.ps1 deleted file mode 100644 index c21321ec9..000000000 --- a/module/PowerShellEditorServices.VSCode/Public/HtmlContentView/Write-VSCodeHtmlContentView.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -function Write-VSCodeHtmlContentView { - <# - .SYNOPSIS - Writes an HTML fragment to an HtmlContentView. - - .DESCRIPTION - Writes an HTML fragment to an HtmlContentView. This new fragment - is appended to the existing content, useful in cases where the - output will be appended to an ongoing output stream. - - .PARAMETER HtmlContentView - The HtmlContentView where content will be appended. - - .PARAMETER AppendedHtmlBodyContent - The HTML content that will be appended to the view's element content. - - .EXAMPLE - Write-VSCodeHtmlContentView -HtmlContentView $htmlContentView -AppendedHtmlBodyContent "

Appended content

" - - .EXAMPLE - Write-VSCodeHtmlContentView -View $htmlContentView -Content "

Appended content

" - #> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [Alias("View")] - [ValidateNotNull()] - [Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentView] - $HtmlContentView, - - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [Alias("Content")] - [ValidateNotNull()] - [string] - $AppendedHtmlBodyContent - ) - - process { - $HtmlContentView.AppendContentAsync($AppendedHtmlBodyContent).Wait(); - } -} diff --git a/module/PowerShellEditorServices.VSCode/docs/Close-VSCodeHtmlContentView.md b/module/PowerShellEditorServices.VSCode/docs/Close-VSCodeHtmlContentView.md new file mode 100644 index 000000000..559d4b9d1 --- /dev/null +++ b/module/PowerShellEditorServices.VSCode/docs/Close-VSCodeHtmlContentView.md @@ -0,0 +1,64 @@ +--- +external help file: Microsoft.PowerShell.EditorServices.VSCode.dll-Help.xml +Module Name: PowerShellEditorServices.VSCode +online version: +schema: 2.0.0 +--- + +# Close-VSCodeHtmlContentView + +## SYNOPSIS + +Closes an HtmlContentView. + +## SYNTAX + +``` +Close-VSCodeHtmlContentView [-HtmlContentView] [] +``` + +## DESCRIPTION + +Closes an HtmlContentView inside of Visual Studio Code if it is displayed. + +## EXAMPLES + +### Example 1 + +```powershell +Close-VSCodeHtmlContentView -HtmlContentView $view +``` + +## PARAMETERS + +### -HtmlContentView + +The HtmlContentView to be closed. + +```yaml +Type: IHtmlContentView +Parameter Sets: (All) +Aliases: View + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### System.Object + +## NOTES + +## RELATED LINKS diff --git a/module/PowerShellEditorServices.VSCode/docs/New-VSCodeHtmlContentView.md b/module/PowerShellEditorServices.VSCode/docs/New-VSCodeHtmlContentView.md new file mode 100644 index 000000000..ec837ddce --- /dev/null +++ b/module/PowerShellEditorServices.VSCode/docs/New-VSCodeHtmlContentView.md @@ -0,0 +1,92 @@ +--- +external help file: Microsoft.PowerShell.EditorServices.VSCode.dll-Help.xml +Module Name: PowerShellEditorServices.VSCode +online version: +schema: 2.0.0 +--- + +# New-VSCodeHtmlContentView + +## SYNOPSIS + +Creates a custom view in Visual Studio Code which displays HTML content. + +## SYNTAX + +``` +New-VSCodeHtmlContentView [-Title] [[-ShowInColumn] ] [] +``` + +## DESCRIPTION + +Creates a custom view in Visual Studio Code which displays HTML content. + +## EXAMPLES + +### Example 1 + +```powershell +$htmlContentView = New-VSCodeHtmlContentView -Title "My Custom View" +``` + +Create a new view called "My Custom View". + +### Example 2 + +```powershell +$htmlContentView = New-VSCodeHtmlContentView -Title "My Custom View" -ShowInColumn Two +``` + +Create a new view and show it in the second view column. + +## PARAMETERS + +### -ShowInColumn + +If specified, causes the new view to be displayed in the specified column. +If unspecified, the Show-VSCodeHtmlContentView cmdlet will need to be used to display the view. + +```yaml +Type: ViewColumn +Parameter Sets: (All) +Aliases: +Accepted values: One, Two, Three + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Title + +The title of the view. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### Microsoft.PowerShell.EditorServices.VSCode.CustomViews.IHtmlContentView + +## NOTES + +## RELATED LINKS diff --git a/module/PowerShellEditorServices.VSCode/docs/Set-VSCodeHtmlContentView.md b/module/PowerShellEditorServices.VSCode/docs/Set-VSCodeHtmlContentView.md new file mode 100644 index 000000000..830ff42ac --- /dev/null +++ b/module/PowerShellEditorServices.VSCode/docs/Set-VSCodeHtmlContentView.md @@ -0,0 +1,123 @@ +--- +external help file: Microsoft.PowerShell.EditorServices.VSCode.dll-Help.xml +Module Name: PowerShellEditorServices.VSCode +online version: +schema: 2.0.0 +--- + +# Set-VSCodeHtmlContentView + +## SYNOPSIS + +Sets the content of an HtmlContentView. + +## SYNTAX + +``` +Set-VSCodeHtmlContentView [-HtmlContentView] [-HtmlBodyContent] + [[-JavaScriptPaths] ] [[-StyleSheetPaths] ] [] +``` + +## DESCRIPTION + +Sets the content of an HtmlContentView. If an empty string is passed, it causes the view's content to be cleared. + +## EXAMPLES + +### Example 1 + +```powershell +Set-VSCodeHtmlContentView -HtmlContentView $htmlContentView -HtmlBodyContent "

Hello world!

" +``` + +Set the view content with an h1 header. + +### Example 2 + +```powershell +Set-VSCodeHtmlContentView -View $htmlContentView -Content "" +``` + +Clear the view. + +## PARAMETERS + +### -HtmlBodyContent + +The HTML content that will be placed inside the `` tag of the view. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: Content + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -HtmlContentView + +The HtmlContentView where content will be set. + +```yaml +Type: IHtmlContentView +Parameter Sets: (All) +Aliases: View + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -JavaScriptPaths + +An array of paths to JavaScript files that will be loaded into the view. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -StyleSheetPaths + +An array of paths to stylesheet (CSS) files that will be loaded into the view. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### System.Object + +## NOTES + +## RELATED LINKS diff --git a/module/PowerShellEditorServices.VSCode/docs/Show-VSCodeHtmlContentView.md b/module/PowerShellEditorServices.VSCode/docs/Show-VSCodeHtmlContentView.md new file mode 100644 index 000000000..2659a3ead --- /dev/null +++ b/module/PowerShellEditorServices.VSCode/docs/Show-VSCodeHtmlContentView.md @@ -0,0 +1,92 @@ +--- +external help file: Microsoft.PowerShell.EditorServices.VSCode.dll-Help.xml +Module Name: PowerShellEditorServices.VSCode +online version: +schema: 2.0.0 +--- + +# Show-VSCodeHtmlContentView + +## SYNOPSIS + +Shows an HtmlContentView. + +## SYNTAX + +``` +Show-VSCodeHtmlContentView [-HtmlContentView] [[-ViewColumn] ] + [] +``` + +## DESCRIPTION + +Shows an HtmlContentView that has been created and not shown yet or has previously been closed. + +## EXAMPLES + +### Example 1 + +```powershell +Show-VSCodeHtmlContentView -HtmlContentView $htmlContentView +``` + +Shows the view in the first editor column. + +### Example 2 + +```powershell +Show-VSCodeHtmlContentView -View $htmlContentView -Column Three +``` + +Shows the view in the third editor column. + +## PARAMETERS + +### -HtmlContentView + +The HtmlContentView that will be shown. + +```yaml +Type: IHtmlContentView +Parameter Sets: (All) +Aliases: View + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ViewColumn + +If specified, causes the new view to be displayed in the specified column. + +```yaml +Type: ViewColumn +Parameter Sets: (All) +Aliases: Column +Accepted values: One, Two, Three + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### System.Object + +## NOTES + +## RELATED LINKS diff --git a/module/PowerShellEditorServices.VSCode/docs/Write-VSCodeHtmlContentView.md b/module/PowerShellEditorServices.VSCode/docs/Write-VSCodeHtmlContentView.md new file mode 100644 index 000000000..79c930da4 --- /dev/null +++ b/module/PowerShellEditorServices.VSCode/docs/Write-VSCodeHtmlContentView.md @@ -0,0 +1,87 @@ +--- +external help file: Microsoft.PowerShell.EditorServices.VSCode.dll-Help.xml +Module Name: PowerShellEditorServices.VSCode +online version: +schema: 2.0.0 +--- + +# Write-VSCodeHtmlContentView + +## SYNOPSIS + +Writes an HTML fragment to an HtmlContentView. + +## SYNTAX + +``` +Write-VSCodeHtmlContentView [-HtmlContentView] [-AppendedHtmlBodyContent] + [] +``` + +## DESCRIPTION + +Writes an HTML fragment to an HtmlContentView. This new fragment is appended to the existing content, useful in cases where the output will be appended to an ongoing output stream. + +## EXAMPLES + +### Example 1 + +```powershell +Write-VSCodeHtmlContentView -HtmlContentView $htmlContentView -AppendedHtmlBodyContent "

Appended content

" +``` + +### Example 2 + +```powershell +Write-VSCodeHtmlContentView -View $htmlContentView -Content "

Appended content

" +``` + +## PARAMETERS + +### -AppendedHtmlBodyContent + +The HTML content that will be appended to the view's `` element content. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: Content + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -HtmlContentView + +The HtmlContentView where content will be appended. + +```yaml +Type: IHtmlContentView +Parameter Sets: (All) +Aliases: View + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String + +## OUTPUTS + +### System.Object + +## NOTES + +## RELATED LINKS diff --git a/src/PowerShellEditorServices.Channel.WebSocket/PowerShellEditorServices.Channel.WebSocket.csproj b/src/PowerShellEditorServices.Channel.WebSocket/PowerShellEditorServices.Channel.WebSocket.csproj deleted file mode 100644 index 612263d23..000000000 --- a/src/PowerShellEditorServices.Channel.WebSocket/PowerShellEditorServices.Channel.WebSocket.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - - PowerShell Editor Services WebSocket Protocol Channel - net452 - Microsoft.PowerShell.EditorServices.Channel.WebSocket - - - - - - - - - - 10.0.3 - - - - - - - - - - - - $(DefineConstants);CoreCLR - - - - - - - diff --git a/src/PowerShellEditorServices.Channel.WebSocket/WebsocketClientChannel.cs b/src/PowerShellEditorServices.Channel.WebSocket/WebsocketClientChannel.cs deleted file mode 100644 index edf2fd52f..000000000 --- a/src/PowerShellEditorServices.Channel.WebSocket/WebsocketClientChannel.cs +++ /dev/null @@ -1,163 +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.IO; -using System.Linq; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Channel.WebSocket -{ - /// - /// Implementation of that enables WebSocket communication. - /// - public class WebsocketClientChannel : ChannelBase - { - private readonly string serverUrl; - private ClientWebSocket socket; - private ClientWebSocketStream inputStream; - private ClientWebSocketStream outputStream; - - /// - /// Gets the process ID of the server process. - /// - public int ProcessId { get; private set; } - - /// - /// Initializes an instance of the WebsocketClientChannel. - /// - /// The full path to the server process executable. - public WebsocketClientChannel(string url) - { - this.serverUrl = url; - } - - public override async Task WaitForConnectionAsync() - { - try - { - await this.socket.ConnectAsync(new Uri(serverUrl), CancellationToken.None); - } - catch (AggregateException ex) - { - var wsException= ex.InnerExceptions.FirstOrDefault() as WebSocketException; - if (wsException != null) - { - Logger.Write(LogLevel.Warning, - string.Format("Failed to connect to WebSocket server. Error was '{0}'", wsException.Message)); - - } - - throw; - } - - this.IsConnected = true; - } - - protected override void Initialize(IMessageSerializer messageSerializer) - { - this.socket = new ClientWebSocket(); - this.inputStream = new ClientWebSocketStream(socket); - this.outputStream = new ClientWebSocketStream(socket); - - // Set up the message reader and writer - this.MessageReader = - new MessageReader( - this.inputStream, - messageSerializer); - - this.MessageWriter = - new MessageWriter( - this.outputStream, - messageSerializer); - } - - protected override void Shutdown() - { - if (this.MessageReader != null) - { - this.MessageReader = null; - } - - if (this.MessageWriter != null) - { - this.MessageWriter = null; - } - - if (this.socket != null) - { - socket.Dispose(); - } - } - } - - /// - /// Extension of that sends data to a WebSocket during FlushAsync - /// and reads during WriteAsync. - /// - internal class ClientWebSocketStream : MemoryStream - { - private readonly ClientWebSocket socket; - - /// - /// Constructor - /// - /// - /// It is expected that the socket is in an Open state. - /// - /// - public ClientWebSocketStream(ClientWebSocket socket) - { - this.socket = socket; - } - - /// - /// Reads from the WebSocket. - /// - /// - /// - /// - /// - /// - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (socket.State != WebSocketState.Open) - { - return 0; - } - - WebSocketReceiveResult result; - do - { - result = await socket.ReceiveAsync(new ArraySegment(buffer, offset, count), cancellationToken); - } while (!result.EndOfMessage); - - if (result.MessageType == WebSocketMessageType.Close) - { - await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); - return 0; - } - - return result.Count; - } - - /// - /// Sends the data in the stream to the buffer and clears the stream. - /// - /// - /// - public override async Task FlushAsync(CancellationToken cancellationToken) - { - await socket.SendAsync(new ArraySegment(ToArray()), WebSocketMessageType.Binary, true, cancellationToken); - SetLength(0); - } - } -} - diff --git a/src/PowerShellEditorServices.Channel.WebSocket/WebsocketServerChannel.cs b/src/PowerShellEditorServices.Channel.WebSocket/WebsocketServerChannel.cs deleted file mode 100644 index c3cb907de..000000000 --- a/src/PowerShellEditorServices.Channel.WebSocket/WebsocketServerChannel.cs +++ /dev/null @@ -1,158 +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.IO; -using System.Linq; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Protocol.Server; -using Owin.WebSocket; - -namespace Microsoft.PowerShell.EditorServices.Channel.WebSocket -{ - /// - /// Implementation of that implements the streams necessary for - /// communicating via OWIN WebSockets. - /// - public class WebSocketServerChannel : ChannelBase - { - private MemoryStream inStream; - private readonly WebSocketConnection socketConnection; - - public WebSocketServerChannel(WebSocketConnection socket) - { - socketConnection = socket; - } - - protected override void Initialize(IMessageSerializer messageSerializer) - { - inStream = new MemoryStream(); - - // Set up the reader and writer - this.MessageReader = - new MessageReader( - this.inStream, - messageSerializer); - - this.MessageWriter = - new MessageWriter( - new WebSocketStream(socketConnection), - messageSerializer); - } - - /// - /// Dispatches data received during calls to OnMessageReceivedAsync in the class. - /// - /// - /// This method calls an overriden version of the that dispatches messages on - /// demand rather than running on a background thread. - /// - /// - /// - public async Task DispatchAsync(ArraySegment message) - { - //Clear our stream - inStream.SetLength(0); - - //Write data and dispatch to handlers - await inStream.WriteAsync(message.ToArray(), 0, message.Count); - inStream.Position = 0; - } - - protected override void Shutdown() - { - this.socketConnection.Close(WebSocketCloseStatus.NormalClosure, "Server shutting down"); - } - - public override Task WaitForConnectionAsync() - { - // TODO: Need to update behavior here - return Task.FromResult(true); - } - } - - /// - /// Overriden that sends data through a during the FlushAsync call. - /// - /// - /// FlushAsync will send data via the SendBinary method of the class. The memory streams length will - /// then be set to 0 to reset the stream for additional data to be written. - /// - internal class WebSocketStream : MemoryStream - { - private readonly WebSocketConnection _connection; - - public WebSocketStream(WebSocketConnection connection) - { - _connection = connection; - } - - public override async Task FlushAsync(CancellationToken cancellationToken) - { - //Send to client socket and reset stream - await _connection.SendBinary(new ArraySegment(ToArray()), true); - SetLength(0); - } - } - - /// - /// Base class for WebSocket connections that expose editor services. - /// - public abstract class EditorServiceWebSocketConnection : WebSocketConnection - { - protected EditorServiceWebSocketConnection() - { - Channel = new WebSocketServerChannel(this); - } - - protected ProtocolEndpoint Server { get; set; } - - protected WebSocketServerChannel Channel { get; private set; } - - public override void OnOpen() - { - Server.Start(); - } - - public override async Task OnMessageReceivedAsync(ArraySegment message, WebSocketMessageType type) - { - await Channel.DispatchAsync(message); - } - - public override Task OnCloseAsync(WebSocketCloseStatus? closeStatus, string closeStatusDescription) - { - Server.Stop(); - - return base.OnCloseAsync(closeStatus, closeStatusDescription); - } - } - - /// - /// Web socket connections that expose the . - /// - public class LanguageServerWebSocketConnection : EditorServiceWebSocketConnection - { - public LanguageServerWebSocketConnection() - { - Server = new LanguageServer(null, null, Channel); - } - } - - /// - /// Web socket connections that expose the . - /// - public class DebugAdapterWebSocketConnection : EditorServiceWebSocketConnection - { - public DebugAdapterWebSocketConnection() - { - Server = new DebugAdapter(null, null, Channel, null); - } - } -} - diff --git a/src/PowerShellEditorServices.Engine/BuildInfo.cs b/src/PowerShellEditorServices.Engine/BuildInfo.cs deleted file mode 100644 index 47c020fbf..000000000 --- a/src/PowerShellEditorServices.Engine/BuildInfo.cs +++ /dev/null @@ -1,14 +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.Engine -{ - public static class BuildInfo - { - public const string BuildVersion = ""; - public const string BuildOrigin = ""; - public static readonly System.DateTime? BuildTime; - } -} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs index a9643104f..3522debb4 100644 --- a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Extensions/EditorObject.cs @@ -42,6 +42,11 @@ public Version EditorServicesVersion /// public EditorWindow Window { get; private set; } + /// + /// Gets the components that are registered. + /// + public IServiceProvider Components => _serviceProvider; + #endregion /// diff --git a/src/PowerShellEditorServices.Host/App.config b/src/PowerShellEditorServices.Host/App.config deleted file mode 100644 index 066f94c5f..000000000 --- a/src/PowerShellEditorServices.Host/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/PowerShellEditorServices.Host/CodeLens/CodeLensExtensions.cs b/src/PowerShellEditorServices.Host/CodeLens/CodeLensExtensions.cs deleted file mode 100644 index c3be72bfe..000000000 --- a/src/PowerShellEditorServices.Host/CodeLens/CodeLensExtensions.cs +++ /dev/null @@ -1,47 +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.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.CodeLenses; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using LanguageServer = Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; - -namespace Microsoft.PowerShell.EditorServices -{ - public static class ICodeLensExtensions - { - public static LanguageServer.CodeLens ToProtocolCodeLens( - this CodeLens codeLens, - JsonSerializer jsonSerializer) - { - return new LanguageServer.CodeLens - { - Range = codeLens.ScriptExtent.ToRange(), - Command = codeLens.Command.ToProtocolCommand(jsonSerializer) - }; - } - - public static LanguageServer.CodeLens ToProtocolCodeLens( - this CodeLens codeLens, - object codeLensData, - JsonSerializer jsonSerializer) - { - LanguageServer.ServerCommand command = null; - - if (codeLens.Command != null) - { - command = codeLens.Command.ToProtocolCommand(jsonSerializer); - } - - return new LanguageServer.CodeLens - { - Range = codeLens.ScriptExtent.ToRange(), - Data = JToken.FromObject(codeLensData, jsonSerializer), - Command = command - }; - } - } -} diff --git a/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs b/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs deleted file mode 100644 index d90d77333..000000000 --- a/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs +++ /dev/null @@ -1,203 +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 Microsoft.PowerShell.EditorServices.Components; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using LanguageServer = Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; - -namespace Microsoft.PowerShell.EditorServices.CodeLenses -{ - /// - /// Implements the CodeLens feature for EditorServices. - /// - internal class CodeLensFeature : - FeatureComponentBase, - ICodeLenses - { - - /// - /// Create a new CodeLens instance around a given editor session - /// from the component registry. - /// - /// - /// The component registry to provider other components and to register the CodeLens provider in. - /// - /// The editor session context of the CodeLens provider. - /// A new CodeLens provider for the given editor session. - public static CodeLensFeature Create( - IComponentRegistry components, - EditorSession editorSession) - { - var codeLenses = - new CodeLensFeature( - editorSession, - JsonSerializer.Create(Constants.JsonSerializerSettings), - components.Get()); - - var messageHandlers = components.Get(); - - messageHandlers.SetRequestHandler( - CodeLensRequest.Type, - codeLenses.HandleCodeLensRequestAsync); - - messageHandlers.SetRequestHandler( - CodeLensResolveRequest.Type, - codeLenses.HandleCodeLensResolveRequestAsync); - - codeLenses.Providers.Add( - new ReferencesCodeLensProvider( - editorSession)); - - codeLenses.Providers.Add( - new PesterCodeLensProvider( - editorSession)); - - editorSession.Components.Register(codeLenses); - - return codeLenses; - } - - /// - /// The editor session context to get workspace and language server data from. - /// - private readonly EditorSession _editorSession; - - /// - /// The json serializer instance for CodeLens object translation. - /// - private readonly JsonSerializer _jsonSerializer; - - /// - /// - /// - /// - /// - /// - private CodeLensFeature( - EditorSession editorSession, - JsonSerializer jsonSerializer, - ILogger logger) - : base(logger) - { - _editorSession = editorSession; - _jsonSerializer = jsonSerializer; - } - - /// - /// Get all the CodeLenses for a given script file. - /// - /// The PowerShell script file to get CodeLenses for. - /// All generated CodeLenses for the given script file. - public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) - { - return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile)) - .SelectMany(codeLens => codeLens) - .ToArray(); - } - - /// - /// Handles a request for CodeLenses from VSCode. - /// - /// Parameters on the CodeLens request that was received. - /// - private async Task HandleCodeLensRequestAsync( - CodeLensRequest codeLensParams, - RequestContext requestContext) - { - ScriptFile scriptFile = _editorSession.Workspace.GetFile( - codeLensParams.TextDocument.Uri); - - CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile); - - var codeLensResponse = new LanguageServer.CodeLens[codeLensResults.Length]; - for (int i = 0; i < codeLensResults.Length; i++) - { - codeLensResponse[i] = codeLensResults[i].ToProtocolCodeLens( - new CodeLensData - { - Uri = codeLensResults[i].File.DocumentUri, - ProviderId = codeLensResults[i].Provider.ProviderId - }, - _jsonSerializer); - } - - await requestContext.SendResultAsync(codeLensResponse); - } - - /// - /// Handle a CodeLens resolve request from VSCode. - /// - /// The CodeLens to be resolved/updated. - /// - private async Task HandleCodeLensResolveRequestAsync( - LanguageServer.CodeLens codeLens, - RequestContext requestContext) - { - if (codeLens.Data != null) - { - // TODO: Catch deserializtion exception on bad object - CodeLensData codeLensData = codeLens.Data.ToObject(); - - ICodeLensProvider originalProvider = - Providers.FirstOrDefault( - provider => provider.ProviderId.Equals(codeLensData.ProviderId)); - - if (originalProvider != null) - { - ScriptFile scriptFile = - _editorSession.Workspace.GetFile( - codeLensData.Uri); - - ScriptRegion region = new ScriptRegion - { - StartLineNumber = codeLens.Range.Start.Line + 1, - StartColumnNumber = codeLens.Range.Start.Character + 1, - EndLineNumber = codeLens.Range.End.Line + 1, - EndColumnNumber = codeLens.Range.End.Character + 1 - }; - - CodeLens originalCodeLens = - new CodeLens( - originalProvider, - scriptFile, - region); - - var resolvedCodeLens = - await originalProvider.ResolveCodeLensAsync( - originalCodeLens, - CancellationToken.None); - - await requestContext.SendResultAsync( - resolvedCodeLens.ToProtocolCodeLens( - _jsonSerializer)); - } - else - { - await requestContext.SendErrorAsync( - $"Could not find provider for the original CodeLens: {codeLensData.ProviderId}"); - } - } - } - - /// - /// Represents data expected back in an LSP CodeLens response. - /// - private class CodeLensData - { - public string Uri { get; set; } - - public string ProviderId {get; set; } - } - } -} diff --git a/src/PowerShellEditorServices.Host/CodeLens/IScriptExtentExtensions.cs b/src/PowerShellEditorServices.Host/CodeLens/IScriptExtentExtensions.cs deleted file mode 100644 index ca8f49d54..000000000 --- a/src/PowerShellEditorServices.Host/CodeLens/IScriptExtentExtensions.cs +++ /dev/null @@ -1,30 +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.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; - -namespace Microsoft.PowerShell.EditorServices -{ - public static class IScriptExtentExtensions - { - public static Range ToRange(this IScriptExtent scriptExtent) - { - return new Range - { - Start = new Position - { - Line = scriptExtent.StartLineNumber - 1, - Character = scriptExtent.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptExtent.EndLineNumber - 1, - Character = scriptExtent.EndColumnNumber - 1 - } - }; - } - } -} diff --git a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs deleted file mode 100644 index cc4a706b0..000000000 --- a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs +++ /dev/null @@ -1,113 +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.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Commands; -using Microsoft.PowerShell.EditorServices.Symbols; - -namespace Microsoft.PowerShell.EditorServices.CodeLenses -{ - internal class PesterCodeLensProvider : FeatureProviderBase, ICodeLensProvider - { - /// - /// The editor session context to provide CodeLenses for. - /// - private EditorSession _editorSession; - - /// - /// The symbol provider to get symbols from to build code lenses with. - /// - private IDocumentSymbolProvider _symbolProvider; - - /// - /// Create a new Pester CodeLens provider for a given editor session. - /// - /// The editor session context for which to provide Pester CodeLenses. - public PesterCodeLensProvider(EditorSession editorSession) - { - _editorSession = editorSession; - _symbolProvider = new PesterDocumentSymbolProvider(); - } - - /// - /// Get the Pester CodeLenses for a given Pester symbol. - /// - /// The Pester symbol to get CodeLenses for. - /// The script file the Pester symbol comes from. - /// All CodeLenses for the given Pester symbol. - private CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, ScriptFile scriptFile) - { - var codeLensResults = new CodeLens[] - { - new CodeLens( - this, - scriptFile, - pesterSymbol.ScriptRegion, - new ClientCommand( - "PowerShell.RunPesterTests", - "Run tests", - new object[] { - scriptFile.DocumentUri, - false /* No debug */, - pesterSymbol.TestName, - pesterSymbol.ScriptRegion?.StartLineNumber })), - - new CodeLens( - this, - scriptFile, - pesterSymbol.ScriptRegion, - new ClientCommand( - "PowerShell.RunPesterTests", - "Debug tests", - new object[] { - scriptFile.DocumentUri, - true /* Run in the debugger */, - pesterSymbol.TestName, - pesterSymbol.ScriptRegion?.StartLineNumber })), - }; - - return codeLensResults; - } - - /// - /// Get all Pester CodeLenses for a given script file. - /// - /// The script file to get Pester CodeLenses for. - /// All Pester CodeLenses for the given script file. - public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) - { - var lenses = new List(); - foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) - { - if (symbol is PesterSymbolReference pesterSymbol) - { - if (pesterSymbol.Command != PesterCommandType.Describe) - { - continue; - } - - lenses.AddRange(GetPesterLens(pesterSymbol, scriptFile)); - } - } - - return lenses.ToArray(); - } - - /// - /// Resolve the CodeLens provision asynchronously -- just wraps the CodeLens argument in a task. - /// - /// The code lens to resolve. - /// - /// The given CodeLens, wrapped in a task. - public Task ResolveCodeLensAsync(CodeLens codeLens, CancellationToken cancellationToken) - { - // This provider has no specific behavior for - // resolving CodeLenses. - return Task.FromResult(codeLens); - } - } -} diff --git a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs deleted file mode 100644 index 69edb6cc3..000000000 --- a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs +++ /dev/null @@ -1,177 +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.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Commands; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Symbols; - -namespace Microsoft.PowerShell.EditorServices.CodeLenses -{ - /// - /// Provides the "reference" code lens by extracting document symbols. - /// - internal class ReferencesCodeLensProvider : FeatureProviderBase, ICodeLensProvider - { - private static readonly Location[] s_emptyLocationArray = new Location[0]; - - /// - /// The editor session code lenses are being provided from. - /// - private EditorSession _editorSession; - - /// - /// The document symbol provider to supply symbols to generate the code lenses. - /// - private IDocumentSymbolProvider _symbolProvider; - - /// - /// Construct a new ReferencesCodeLensProvider for a given EditorSession. - /// - /// - public ReferencesCodeLensProvider(EditorSession editorSession) - { - _editorSession = editorSession; - - // TODO: Pull this from components - _symbolProvider = new ScriptDocumentSymbolProvider( - editorSession.PowerShellContext.LocalPowerShellVersion.Version); - } - - /// - /// Get all reference code lenses for a given script file. - /// - /// The PowerShell script file to get code lenses for. - /// An array of CodeLenses describing all functions in the given script file. - public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) - { - var acc = new List(); - foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile)) - { - if (sym.SymbolType == SymbolType.Function) - { - acc.Add(new CodeLens(this, scriptFile, sym.ScriptRegion)); - } - } - - return acc.ToArray(); - } - - /// - /// Take a codelens and create a new codelens object with updated references. - /// - /// The old code lens to get updated references for. - /// The cancellation token for this request. - /// A new code lens object describing the same data as the old one but with updated references. - public async Task ResolveCodeLensAsync( - CodeLens codeLens, - CancellationToken cancellationToken) - { - ScriptFile[] references = _editorSession.Workspace.ExpandScriptReferences( - codeLens.File); - - SymbolReference foundSymbol = _editorSession.LanguageService.FindFunctionDefinitionAtLocation( - codeLens.File, - codeLens.ScriptExtent.StartLineNumber, - codeLens.ScriptExtent.StartColumnNumber); - - FindReferencesResult referencesResult = await _editorSession.LanguageService.FindReferencesOfSymbolAsync( - foundSymbol, - references, - _editorSession.Workspace); - - Location[] referenceLocations; - if (referencesResult == null) - { - referenceLocations = s_emptyLocationArray; - } - else - { - var acc = new List(); - foreach (SymbolReference foundReference in referencesResult.FoundReferences) - { - if (!NotReferenceDefinition(foundSymbol, foundReference)) - { - continue; - } - - acc.Add(new Location - { - Uri = GetFileUri(foundReference.FilePath), - Range = foundReference.ScriptRegion.ToRange() - }); - } - referenceLocations = acc.ToArray(); - } - - return new CodeLens( - codeLens, - new ClientCommand( - "editor.action.showReferences", - GetReferenceCountHeader(referenceLocations.Length), - new object[] - { - codeLens.File.DocumentUri, - codeLens.ScriptExtent.ToRange().Start, - referenceLocations, - } - )); - } - - /// - /// Check whether a SymbolReference is not a reference to another defined symbol. - /// - /// The symbol definition that may be referenced. - /// The reference symbol to check. - /// True if the reference is not a reference to the definition, false otherwise. - private static bool NotReferenceDefinition( - SymbolReference definition, - SymbolReference reference) - { - return - definition.ScriptRegion.StartLineNumber != reference.ScriptRegion.StartLineNumber - || definition.SymbolType != reference.SymbolType - || !string.Equals(definition.SymbolName, reference.SymbolName, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Get a URI for a given file path. - /// - /// A file path that may be prefixed with URI scheme already. - /// A URI to the file. - private static string GetFileUri(string filePath) - { - // If the file isn't untitled, return a URI-style path - return - !filePath.StartsWith("untitled") && !filePath.StartsWith("inmemory") - ? Workspace.ConvertPathToDocumentUri(filePath) - : filePath; - } - - /// - /// Get the code lens header for the number of references on a definition, - /// given the number of references. - /// - /// The number of references found for a given definition. - /// The header string for the reference code lens. - private static string GetReferenceCountHeader(int referenceCount) - { - if (referenceCount == 1) - { - return "1 reference"; - } - - var sb = new StringBuilder(14); // "100 references".Length = 14 - sb.Append(referenceCount); - sb.Append(" references"); - return sb.ToString(); - } - } -} diff --git a/src/PowerShellEditorServices.Host/Commands/ClientCommandExtensions.cs b/src/PowerShellEditorServices.Host/Commands/ClientCommandExtensions.cs deleted file mode 100644 index 95594d9b6..000000000 --- a/src/PowerShellEditorServices.Host/Commands/ClientCommandExtensions.cs +++ /dev/null @@ -1,31 +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 Microsoft.PowerShell.EditorServices.Commands; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using LanguageServer = Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; - -namespace Microsoft.PowerShell.EditorServices -{ - public static class ClientCommandExtensions - { - public static LanguageServer.ServerCommand ToProtocolCommand( - this ClientCommand clientCommand, - JsonSerializer jsonSerializer) - { - return new LanguageServer.ServerCommand - { - Command = clientCommand.Name, - Title = clientCommand.Title, - Arguments = - JArray.FromObject( - clientCommand.Arguments, - jsonSerializer) - }; - } - } -} diff --git a/src/PowerShellEditorServices.Host/EditorServicesHost.cs b/src/PowerShellEditorServices.Host/EditorServicesHost.cs deleted file mode 100644 index 15ace52f8..000000000 --- a/src/PowerShellEditorServices.Host/EditorServicesHost.cs +++ /dev/null @@ -1,563 +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 Microsoft.PowerShell.EditorServices.Components; -using Microsoft.PowerShell.EditorServices.CodeLenses; -using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Protocol.Server; -using Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Symbols; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Host; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Host -{ - public enum EditorServicesHostStatus - { - Started, - Failed, - Ended - } - - public enum EditorServiceTransportType - { - NamedPipe, - Stdio - } - - public class EditorServiceTransportConfig - { - public EditorServiceTransportType TransportType { get; set; } - /// - /// Configures the endpoint of the transport. - /// For Stdio it's ignored. - /// For NamedPipe it's the pipe name. - /// - public string InOutPipeName { get; set; } - - public string OutPipeName { get; set; } - - public string InPipeName { get; set; } - - internal string Endpoint => OutPipeName != null && InPipeName != null ? $"In pipe: {InPipeName} Out pipe: {OutPipeName}" : $" InOut pipe: {InOutPipeName}"; - } - - /// - /// Provides a simplified interface for hosting the language and debug services - /// over the named pipe server protocol. - /// - public class EditorServicesHost - { - #region Private Fields - - private readonly PSHost internalHost; - private string[] additionalModules; - private string bundledModulesPath; - private DebugAdapter debugAdapter; - private EditorSession editorSession; - private bool enableConsoleRepl; - private HashSet featureFlags; - private HostDetails hostDetails; - private LanguageServer languageServer; - private ILogger logger; - private ProfilePaths profilePaths; - private TaskCompletionSource serverCompletedTask; - - private IServerListener languageServiceListener; - private IServerListener debugServiceListener; - - #endregion - - #region Properties - - public EditorServicesHostStatus Status { get; private set; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the EditorServicesHost class and waits for - /// the debugger to attach if waitForDebugger is true. - /// - /// The details of the host which is launching PowerShell Editor Services. - /// Provides a path to PowerShell modules bundled with the host, if any. Null otherwise. - /// If true, causes the host to wait for the debugger to attach before proceeding. - /// Modules to be loaded when initializing the new runspace. - /// Features to enable for this instance. - public EditorServicesHost( - HostDetails hostDetails, - string bundledModulesPath, - bool enableConsoleRepl, - bool waitForDebugger, - string[] additionalModules, - string[] featureFlags) - : this( - hostDetails, - bundledModulesPath, - enableConsoleRepl, - waitForDebugger, - additionalModules, - featureFlags, - GetInternalHostFromDefaultRunspace()) - { - } - - /// - /// Initializes a new instance of the EditorServicesHost class and waits for - /// the debugger to attach if waitForDebugger is true. - /// - /// The details of the host which is launching PowerShell Editor Services. - /// Provides a path to PowerShell modules bundled with the host, if any. Null otherwise. - /// If true, causes the host to wait for the debugger to attach before proceeding. - /// Modules to be loaded when initializing the new runspace. - /// Features to enable for this instance. - /// The value of the $Host variable in the original runspace. - public EditorServicesHost( - HostDetails hostDetails, - string bundledModulesPath, - bool enableConsoleRepl, - bool waitForDebugger, - string[] additionalModules, - string[] featureFlags, - PSHost internalHost) - { - Validate.IsNotNull(nameof(hostDetails), hostDetails); - Validate.IsNotNull(nameof(internalHost), internalHost); - - this.hostDetails = hostDetails; - this.enableConsoleRepl = enableConsoleRepl; - this.bundledModulesPath = bundledModulesPath; - this.additionalModules = additionalModules ?? new string[0]; - this.featureFlags = new HashSet(featureFlags ?? new string[0]); - this.serverCompletedTask = new TaskCompletionSource(); - this.internalHost = internalHost; - - while (!System.Diagnostics.Debugger.IsAttached) - { - System.Console.WriteLine(System.Diagnostics.Process.GetCurrentProcess().Id); - System.Threading.Thread.Sleep(2000); - } - -#if DEBUG - if (waitForDebugger) - { - if (System.Diagnostics.Debugger.IsAttached) - { - System.Diagnostics.Debugger.Break(); - } - else - { - System.Diagnostics.Debugger.Launch(); - } - } -#endif - - // Catch unhandled exceptions for logging purposes - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - } - - #endregion - - #region Public Methods - - /// - /// Starts the Logger for the specified file path and log level. - /// - /// The path of the log file to be written. - /// The minimum level of log messages to be written. - public void StartLogging(string logFilePath, LogLevel logLevel) - { - this.logger = Logging.CreateLogger() - .LogLevel(logLevel) - .AddLogFile(logFilePath) - .Build(); - - FileVersionInfo fileVersionInfo = - FileVersionInfo.GetVersionInfo(this.GetType().GetTypeInfo().Assembly.Location); - - string osVersion = RuntimeInformation.OSDescription; - - string osArch = GetOSArchitecture(); - - string buildTime = BuildInfo.BuildTime?.ToString("s", System.Globalization.CultureInfo.InvariantCulture) ?? ""; - - string logHeader = $@" -PowerShell Editor Services Host v{fileVersionInfo.FileVersion} starting (PID {Process.GetCurrentProcess().Id} - - Host application details: - - Name: {this.hostDetails.Name} - Version: {this.hostDetails.Version} - ProfileId: {this.hostDetails.ProfileId} - Arch: {osArch} - - Operating system details: - - Version: {osVersion} - Arch: {osArch} - - Build information: - - Version: {BuildInfo.BuildVersion} - Origin: {BuildInfo.BuildOrigin} - Date: {buildTime} -"; - - this.logger.Write(LogLevel.Normal, logHeader); - } - - /// - /// Starts the language service with the specified config. - /// - /// The config that contains information on the communication protocol that will be used. - /// The profiles that will be loaded in the session. - public void StartLanguageService( - EditorServiceTransportConfig config, - ProfilePaths profilePaths) - { - this.profilePaths = profilePaths; - - this.languageServiceListener = CreateServiceListener(MessageProtocolType.LanguageServer, config); - - this.languageServiceListener.ClientConnect += this.OnLanguageServiceClientConnectAsync; - this.languageServiceListener.Start(); - - this.logger.Write( - LogLevel.Normal, - string.Format( - "Language service started, type = {0}, endpoint = {1}", - config.TransportType, config.Endpoint)); - } - - private async void OnLanguageServiceClientConnectAsync( - object sender, - ChannelBase serverChannel) - { - MessageDispatcher messageDispatcher = new MessageDispatcher(this.logger); - - ProtocolEndpoint protocolEndpoint = - new ProtocolEndpoint( - serverChannel, - messageDispatcher, - this.logger); - - protocolEndpoint.UnhandledException += ProtocolEndpoint_UnhandledException; - - this.editorSession = - CreateSession( - this.hostDetails, - this.profilePaths, - protocolEndpoint, - messageDispatcher, - this.enableConsoleRepl); - - this.languageServer = - new LanguageServer( - this.editorSession, - messageDispatcher, - protocolEndpoint, - this.serverCompletedTask, - this.logger); - - await this.editorSession.PowerShellContext.ImportCommandsModuleAsync( - Path.Combine( - Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), - @"..\Commands")); - - this.languageServer.Start(); - - // TODO: This can be moved to the point after the $psEditor object - // gets initialized when that is done earlier than LanguageServer.Initialize - foreach (string module in this.additionalModules) - { - var command = - new System.Management.Automation.PSCommand() - .AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("Name", module); - - await this.editorSession.PowerShellContext.ExecuteCommandAsync( - command, - sendOutputToHost: false, - sendErrorToHost: true); - } - - protocolEndpoint.Start(); - } - - /// - /// Starts the debug service with the specified config. - /// - /// The config that contains information on the communication protocol that will be used. - /// The profiles that will be loaded in the session. - /// Determines if we will reuse the session that we have. - public void StartDebugService( - EditorServiceTransportConfig config, - ProfilePaths profilePaths, - bool useExistingSession) - { - this.debugServiceListener = CreateServiceListener(MessageProtocolType.DebugAdapter, config); - this.debugServiceListener.ClientConnect += OnDebugServiceClientConnect; - this.debugServiceListener.Start(); - - this.logger.Write( - LogLevel.Normal, - string.Format( - "Debug service started, type = {0}, endpoint = {1}", - config.TransportType, config.Endpoint)); - } - - private void OnDebugServiceClientConnect(object sender, ChannelBase serverChannel) - { - MessageDispatcher messageDispatcher = new MessageDispatcher(this.logger); - - ProtocolEndpoint protocolEndpoint = - new ProtocolEndpoint( - serverChannel, - messageDispatcher, - this.logger); - - protocolEndpoint.UnhandledException += ProtocolEndpoint_UnhandledException; - - bool ownsEditorSession = this.editorSession == null; - - if (ownsEditorSession) - { - this.editorSession = - this.CreateDebugSession( - this.hostDetails, - profilePaths, - protocolEndpoint, - messageDispatcher, - this.languageServer?.EditorOperations, - this.enableConsoleRepl); - } - - this.debugAdapter = - new DebugAdapter( - this.editorSession, - ownsEditorSession, - messageDispatcher, - protocolEndpoint, - this.logger); - - this.debugAdapter.SessionEnded += - (obj, args) => - { - if (!ownsEditorSession) - { - this.logger.Write( - LogLevel.Normal, - "Previous debug session ended, restarting debug service listener..."); - this.debugServiceListener.Stop(); - this.debugServiceListener.Start(); - } - else if (this.debugAdapter.IsUsingTempIntegratedConsole) - { - this.logger.Write( - LogLevel.Normal, - "Previous temp debug session ended"); - } - else - { - // Exit the host process - this.serverCompletedTask.SetResult(true); - } - }; - - this.debugAdapter.Start(); - protocolEndpoint.Start(); - } - - /// - /// Stops the language or debug services if either were started. - /// - public void StopServices() - { - // TODO: Need a new way to shut down the services - - this.languageServer = null; - - this.debugAdapter = null; - } - - /// - /// Waits for either the language or debug service to shut down. - /// - public void WaitForCompletion() - { - // TODO: We need a way to know when to complete this task! - this.serverCompletedTask.Task.Wait(); - } - - #endregion - - #region Private Methods - - private static PSHost GetInternalHostFromDefaultRunspace() - { - using (var pwsh = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - return pwsh.AddScript("$Host").Invoke().First(); - } - } - - private EditorSession CreateSession( - HostDetails hostDetails, - ProfilePaths profilePaths, - IMessageSender messageSender, - IMessageHandlers messageHandlers, - bool enableConsoleRepl) - { - EditorSession editorSession = new EditorSession(this.logger); - PowerShellContext powerShellContext = new PowerShellContext(this.logger, this.featureFlags.Contains("PSReadLine")); - - EditorServicesPSHostUserInterface hostUserInterface = - enableConsoleRepl - ? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, this.logger, this.internalHost) - : new ProtocolPSHostUserInterface(powerShellContext, messageSender, this.logger); - - EditorServicesPSHost psHost = - new EditorServicesPSHost( - powerShellContext, - hostDetails, - hostUserInterface, - this.logger); - - Runspace initialRunspace = PowerShellContext.CreateRunspace(psHost); - powerShellContext.Initialize(profilePaths, initialRunspace, true, hostUserInterface); - - editorSession.StartSession(powerShellContext, hostUserInterface); - - // TODO: Move component registrations elsewhere! - editorSession.Components.Register(this.logger); - editorSession.Components.Register(messageHandlers); - editorSession.Components.Register(messageSender); - editorSession.Components.Register(powerShellContext); - - CodeLensFeature.Create(editorSession.Components, editorSession); - DocumentSymbolFeature.Create(editorSession.Components, editorSession); - - return editorSession; - } - - private EditorSession CreateDebugSession( - HostDetails hostDetails, - ProfilePaths profilePaths, - IMessageSender messageSender, - IMessageHandlers messageHandlers, - IEditorOperations editorOperations, - bool enableConsoleRepl) - { - EditorSession editorSession = new EditorSession(this.logger); - PowerShellContext powerShellContext = new PowerShellContext( - this.logger, - this.featureFlags.Contains("PSReadLine")); - - EditorServicesPSHostUserInterface hostUserInterface = - enableConsoleRepl - ? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, this.logger, this.internalHost) - : new ProtocolPSHostUserInterface(powerShellContext, messageSender, this.logger); - - EditorServicesPSHost psHost = - new EditorServicesPSHost( - powerShellContext, - hostDetails, - hostUserInterface, - this.logger); - - Runspace initialRunspace = PowerShellContext.CreateRunspace(psHost); - powerShellContext.Initialize(profilePaths, initialRunspace, true, hostUserInterface); - - editorSession.StartDebugSession( - powerShellContext, - hostUserInterface, - editorOperations); - - return editorSession; - } - - private void ProtocolEndpoint_UnhandledException(object sender, Exception e) - { - this.logger.Write( - LogLevel.Error, - "PowerShell Editor Services is terminating due to an unhandled exception, see previous logs for details."); - - this.serverCompletedTask.SetException(e); - } - - private void CurrentDomain_UnhandledException( - object sender, - UnhandledExceptionEventArgs e) - { - // Log the exception - this.logger.Write(LogLevel.Error, $"FATAL UNHANDLED EXCEPTION: {e.ExceptionObject}"); - } - - private IServerListener CreateServiceListener(MessageProtocolType protocol, EditorServiceTransportConfig config) - { - switch (config.TransportType) - { - case EditorServiceTransportType.Stdio: - { - return new StdioServerListener(protocol, this.logger); - } - - case EditorServiceTransportType.NamedPipe: - { - if ((config.OutPipeName != null) && (config.InPipeName != null)) - { - this.logger.Write(LogLevel.Verbose, $"Creating NamedPipeServerListener for ${protocol} protocol with two pipes: In: '{config.InPipeName}'. Out: '{config.OutPipeName}'"); - return new NamedPipeServerListener(protocol, config.InPipeName, config.OutPipeName, this.logger); - } - else - { - return new NamedPipeServerListener(protocol, config.InOutPipeName, this.logger); - } - } - - default: - { - throw new NotSupportedException(); - } - } - } - - /// - /// Gets the OSArchitecture for logging. Cannot use System.Runtime.InteropServices.RuntimeInformation.OSArchitecture - /// directly, since this tries to load API set DLLs in win7 and crashes. - /// - /// - private string GetOSArchitecture() - { - // If on win7 (version 6.1.x), avoid System.Runtime.InteropServices.RuntimeInformation - if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version < new Version(6, 2)) - { - if (Environment.Is64BitProcess) - { - return "X64"; - } - - return "X86"; - } - - return RuntimeInformation.OSArchitecture.ToString(); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Host/PSHost/PromptHandlers.cs b/src/PowerShellEditorServices.Host/PSHost/PromptHandlers.cs deleted file mode 100644 index d8ec88c4e..000000000 --- a/src/PowerShellEditorServices.Host/PSHost/PromptHandlers.cs +++ /dev/null @@ -1,185 +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 Microsoft.PowerShell.EditorServices.Console; -using Microsoft.PowerShell.EditorServices.Protocol.Messages; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; -using System.Threading.Tasks; -using System.Threading; -using System.Security; - -namespace Microsoft.PowerShell.EditorServices.Host -{ - internal class ProtocolChoicePromptHandler : ConsoleChoicePromptHandler - { - private IHostInput hostInput; - private IMessageSender messageSender; - private TaskCompletionSource readLineTask; - - public ProtocolChoicePromptHandler( - IMessageSender messageSender, - IHostInput hostInput, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - this.hostInput = hostInput; - this.hostOutput = hostOutput; - this.messageSender = messageSender; - } - - protected override void ShowPrompt(PromptStyle promptStyle) - { - base.ShowPrompt(promptStyle); - - messageSender - .SendRequestAsync( - ShowChoicePromptRequest.Type, - new ShowChoicePromptRequest - { - IsMultiChoice = this.IsMultiChoice, - Caption = this.Caption, - Message = this.Message, - Choices = this.Choices, - DefaultChoices = this.DefaultChoices - }, true) - .ContinueWith(HandlePromptResponse) - .ConfigureAwait(false); - } - - protected override Task ReadInputStringAsync(CancellationToken cancellationToken) - { - this.readLineTask = new TaskCompletionSource(); - return this.readLineTask.Task; - } - - private void HandlePromptResponse( - Task responseTask) - { - if (responseTask.IsCompleted) - { - ShowChoicePromptResponse response = responseTask.Result; - - if (!response.PromptCancelled) - { - this.hostOutput.WriteOutput( - response.ResponseText, - OutputType.Normal); - - this.readLineTask.TrySetResult(response.ResponseText); - } - else - { - // Cancel the current prompt - this.hostInput.SendControlC(); - } - } - else - { - if (responseTask.IsFaulted) - { - // Log the error - Logger.Write( - LogLevel.Error, - "ShowChoicePrompt request failed with error:\r\n{0}", - responseTask.Exception.ToString()); - } - - // Cancel the current prompt - this.hostInput.SendControlC(); - } - - this.readLineTask = null; - } - } - - internal class ProtocolInputPromptHandler : ConsoleInputPromptHandler - { - private IHostInput hostInput; - private IMessageSender messageSender; - private TaskCompletionSource readLineTask; - - public ProtocolInputPromptHandler( - IMessageSender messageSender, - IHostInput hostInput, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - this.hostInput = hostInput; - this.hostOutput = hostOutput; - this.messageSender = messageSender; - } - - protected override void ShowFieldPrompt(FieldDetails fieldDetails) - { - base.ShowFieldPrompt(fieldDetails); - - messageSender - .SendRequestAsync( - ShowInputPromptRequest.Type, - new ShowInputPromptRequest - { - Name = fieldDetails.Name, - Label = fieldDetails.Label - }, true) - .ContinueWith(HandlePromptResponse) - .ConfigureAwait(false); - } - - protected override Task ReadInputStringAsync(CancellationToken cancellationToken) - { - this.readLineTask = new TaskCompletionSource(); - return this.readLineTask.Task; - } - - private void HandlePromptResponse( - Task responseTask) - { - if (responseTask.IsCompleted) - { - ShowInputPromptResponse response = responseTask.Result; - - if (!response.PromptCancelled) - { - this.hostOutput.WriteOutput( - response.ResponseText, - OutputType.Normal); - - this.readLineTask.TrySetResult(response.ResponseText); - } - else - { - // Cancel the current prompt - this.hostInput.SendControlC(); - } - } - else - { - if (responseTask.IsFaulted) - { - // Log the error - Logger.Write( - LogLevel.Error, - "ShowInputPrompt request failed with error:\r\n{0}", - responseTask.Exception.ToString()); - } - - // Cancel the current prompt - this.hostInput.SendControlC(); - } - - this.readLineTask = null; - } - - protected override Task ReadSecureStringAsync(CancellationToken cancellationToken) - { - // TODO: Write a message to the console - throw new NotImplementedException(); - } - } -} diff --git a/src/PowerShellEditorServices.Host/PSHost/ProtocolPSHostUserInterface.cs b/src/PowerShellEditorServices.Host/PSHost/ProtocolPSHostUserInterface.cs deleted file mode 100644 index 76b1f7252..000000000 --- a/src/PowerShellEditorServices.Host/PSHost/ProtocolPSHostUserInterface.cs +++ /dev/null @@ -1,123 +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 Microsoft.PowerShell.EditorServices.Console; -using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.Server; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Host -{ - internal class ProtocolPSHostUserInterface : EditorServicesPSHostUserInterface - { - #region Private Fields - - private IMessageSender messageSender; - private OutputDebouncer outputDebouncer; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHostUserInterface - /// class with the given IConsoleHost implementation. - /// - /// - public ProtocolPSHostUserInterface( - PowerShellContext powerShellContext, - IMessageSender messageSender, - ILogger logger) - : base(powerShellContext, new SimplePSHostRawUserInterface(logger), logger) - { - this.messageSender = messageSender; - this.outputDebouncer = new OutputDebouncer(messageSender); - } - - public void Dispose() - { - // TODO: Need a clear API path for this - - // Make sure remaining output is flushed before exiting - if (this.outputDebouncer != null) - { - this.outputDebouncer.FlushAsync().Wait(); - this.outputDebouncer = null; - } - } - - #endregion - - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - public override void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor) - { - // TODO: This should use a synchronous method! - this.outputDebouncer.InvokeAsync( - new OutputWrittenEventArgs( - outputString, - includeNewLine, - outputType, - foregroundColor, - backgroundColor)).Wait(); - } - - /// - /// Sends a progress update event to the user. - /// - /// The source ID of the progress event. - /// The details of the activity's current progress. - protected override void UpdateProgress( - long sourceId, - ProgressDetails progressDetails) - { - } - - protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - // This currently does nothing because the "evaluate" request - // will cancel the current prompt and execute the user's - // script selection. - return new TaskCompletionSource().Task; - } - - protected override InputPromptHandler OnCreateInputPromptHandler() - { - return new ProtocolInputPromptHandler(this.messageSender, this, this, this.Logger); - } - - protected override ChoicePromptHandler OnCreateChoicePromptHandler() - { - return new ProtocolChoicePromptHandler(this.messageSender, this, this, this.Logger); - } - } -} diff --git a/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj b/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj deleted file mode 100644 index 2245a4a22..000000000 --- a/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - - PowerShell Editor Services Host Process - Provides a process for hosting the PowerShell Editor Services library exposed by a JSON message protocol. - netstandard2.0 - Microsoft.PowerShell.EditorServices.Host - - - - - - - - - - - diff --git a/src/PowerShellEditorServices.Host/Symbols/DocumentSymbolFeature.cs b/src/PowerShellEditorServices.Host/Symbols/DocumentSymbolFeature.cs deleted file mode 100644 index da3402bde..000000000 --- a/src/PowerShellEditorServices.Host/Symbols/DocumentSymbolFeature.cs +++ /dev/null @@ -1,115 +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 Microsoft.PowerShell.EditorServices.Components; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -using Servers = Microsoft.PowerShell.EditorServices.Protocol.Server; - -namespace Microsoft.PowerShell.EditorServices.Symbols -{ - internal class DocumentSymbolFeature : - FeatureComponentBase, - IDocumentSymbols - { - private EditorSession editorSession; - - public DocumentSymbolFeature( - EditorSession editorSession, - IMessageHandlers messageHandlers, - ILogger logger) - : base(logger) - { - this.editorSession = editorSession; - - messageHandlers.SetRequestHandler( - DocumentSymbolRequest.Type, - this.HandleDocumentSymbolRequestAsync); - } - - public static DocumentSymbolFeature Create( - IComponentRegistry components, - EditorSession editorSession) - { - var documentSymbols = - new DocumentSymbolFeature( - editorSession, - components.Get(), - components.Get()); - - documentSymbols.Providers.Add( - new ScriptDocumentSymbolProvider( - editorSession.PowerShellContext.LocalPowerShellVersion.Version)); - - documentSymbols.Providers.Add( - new PsdDocumentSymbolProvider()); - - documentSymbols.Providers.Add( - new PesterDocumentSymbolProvider()); - - editorSession.Components.Register(documentSymbols); - - return documentSymbols; - } - - public IEnumerable ProvideDocumentSymbols( - ScriptFile scriptFile) - { - return - this.InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile)) - .SelectMany(r => r); - } - - protected async Task HandleDocumentSymbolRequestAsync( - DocumentSymbolParams documentSymbolParams, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - documentSymbolParams.TextDocument.Uri); - - IEnumerable foundSymbols = - this.ProvideDocumentSymbols(scriptFile); - - SymbolInformation[] symbols = null; - - string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); - - if (foundSymbols != null) - { - symbols = - foundSymbols - .Select(r => - { - return new SymbolInformation - { - ContainerName = containerName, - Kind = Servers.LanguageServer.GetSymbolKind(r.SymbolType), - Location = new Location - { - Uri = Servers.LanguageServer.GetFileUri(r.FilePath), - Range = Servers.LanguageServer.GetRangeFromScriptRegion(r.ScriptRegion) - }, - Name = Servers.LanguageServer.GetDecoratedSymbolName(r) - }; - }) - .ToArray(); - } - else - { - symbols = new SymbolInformation[0]; - } - - await requestContext.SendResultAsync(symbols); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs b/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs deleted file mode 100644 index b226fd0c4..000000000 --- a/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs +++ /dev/null @@ -1,97 +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 Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Utility; -using System.Threading.Tasks; -using System; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Client -{ - public class DebugAdapterClient : IMessageSender, IMessageHandlers - { - private ILogger logger; - private ProtocolEndpoint protocolEndpoint; - private MessageDispatcher messageDispatcher; - - public DebugAdapterClient(ChannelBase clientChannel, ILogger logger) - { - this.logger = logger; - this.messageDispatcher = new MessageDispatcher(logger); - this.protocolEndpoint = new ProtocolEndpoint( - clientChannel, - messageDispatcher, - logger); - } - - public async Task StartAsync() - { - this.protocolEndpoint.Start(); - - // Initialize the debug adapter - await this.SendRequestAsync( - InitializeRequest.Type, - new InitializeRequestArguments - { - LinesStartAt1 = true, - ColumnsStartAt1 = true - }, - true); - } - - public void Stop() - { - this.protocolEndpoint.Stop(); - } - - public async Task LaunchScriptAsync(string scriptFilePath) - { - await this.SendRequestAsync( - LaunchRequest.Type, - new LaunchRequestArguments { - Script = scriptFilePath - }, - true); - - await this.SendRequestAsync( - ConfigurationDoneRequest.Type, - null, - true); - } - - public Task SendEventAsync(NotificationType eventType, TParams eventParams) - { - return ((IMessageSender)protocolEndpoint).SendEventAsync(eventType, eventParams); - } - - public Task SendRequestAsync(RequestType requestType, TParams requestParams, bool waitForResponse) - { - return ((IMessageSender)protocolEndpoint).SendRequestAsync(requestType, requestParams, waitForResponse); - } - - public Task SendRequestAsync(RequestType0 requestType0) - { - return ((IMessageSender)protocolEndpoint).SendRequestAsync(requestType0); - } - - public void SetRequestHandler(RequestType requestType, Func, Task> requestHandler) - { - ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType, requestHandler); - } - - public void SetRequestHandler(RequestType0 requestType0, Func, Task> requestHandler) - { - ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType0, requestHandler); - } - - public void SetEventHandler(NotificationType eventType, Func eventHandler) - { - ((IMessageHandlers)messageDispatcher).SetEventHandler(eventType, eventHandler); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs b/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs deleted file mode 100644 index 0ccb3d5b5..000000000 --- a/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs +++ /dev/null @@ -1,102 +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 Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Client -{ - /// - /// Provides a base implementation for language server clients. - /// - public abstract class LanguageClientBase : IMessageHandlers, IMessageSender - { - ILogger logger; - private ProtocolEndpoint protocolEndpoint; - private MessageDispatcher messageDispatcher; - - /// - /// Initializes an instance of the language client using the - /// specified channel for communication. - /// - /// The channel to use for communication with the server. - public LanguageClientBase(ChannelBase clientChannel, ILogger logger) - { - this.logger = logger; - this.messageDispatcher = new MessageDispatcher(logger); - this.protocolEndpoint = new ProtocolEndpoint( - clientChannel, - messageDispatcher, - logger); - } - - public Task Start() - { - this.protocolEndpoint.Start(); - - // Initialize the implementation class - return this.InitializeAsync(); - } - - public async Task StopAsync() - { - await this.OnStopAsync(); - - // First, notify the language server that we're stopping - var response = - await this.SendRequestAsync( - ShutdownRequest.Type); - - await this.SendEventAsync(ExitNotification.Type, new object()); - - this.protocolEndpoint.Stop(); - } - - protected virtual Task OnStopAsync() - { - return Task.FromResult(true); - } - - protected virtual Task InitializeAsync() - { - return Task.FromResult(true); - } - - public Task SendEventAsync(NotificationType eventType, TParams eventParams) - { - return ((IMessageSender)protocolEndpoint).SendEventAsync(eventType, eventParams); - } - - public Task SendRequestAsync(RequestType requestType, TParams requestParams, bool waitForResponse) - { - return ((IMessageSender)protocolEndpoint).SendRequestAsync(requestType, requestParams, waitForResponse); - } - - public Task SendRequestAsync(RequestType0 requestType0) - { - return ((IMessageSender)protocolEndpoint).SendRequestAsync(requestType0); - } - - public void SetRequestHandler(RequestType requestType, Func, Task> requestHandler) - { - ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType, requestHandler); - } - - public void SetRequestHandler(RequestType0 requestType0, Func, Task> requestHandler) - { - ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType0, requestHandler); - } - - public void SetEventHandler(NotificationType eventType, Func eventHandler) - { - ((IMessageHandlers)messageDispatcher).SetEventHandler(eventType, eventHandler); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs b/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs deleted file mode 100644 index e2b1491fd..000000000 --- a/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs +++ /dev/null @@ -1,118 +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 Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Client -{ - public class LanguageServiceClient : LanguageClientBase - { - private Dictionary cachedDiagnostics = - new Dictionary(); - - public LanguageServiceClient(ChannelBase clientChannel, ILogger logger) - : base(clientChannel, logger) - { - } - - protected override Task InitializeAsync() - { - // Add handlers for common events - this.SetEventHandler(PublishDiagnosticsNotification.Type, HandlePublishDiagnosticsEventAsync); - - // Send the 'initialize' request and wait for the response - var initializeParams = new InitializeParams - { - RootPath = "", - Capabilities = new ClientCapabilities() - }; - - return this.SendRequestAsync( - InitializeRequest.Type, - initializeParams, - true); - } - - #region Events - - public event EventHandler DiagnosticsReceived; - - protected void OnDiagnosticsReceived(string filePath) - { - if (this.DiagnosticsReceived != null) - { - this.DiagnosticsReceived(this, filePath); - } - } - - #endregion - - #region Private Methods - - private Task HandlePublishDiagnosticsEventAsync( - PublishDiagnosticsNotification diagnostics, - EventContext eventContext) - { - string normalizedPath = diagnostics.Uri.ToLower(); - - this.cachedDiagnostics[normalizedPath] = - diagnostics.Diagnostics - .Select(GetMarkerFromDiagnostic) - .ToArray(); - - this.OnDiagnosticsReceived(normalizedPath); - - return Task.FromResult(true); - } - - private static ScriptFileMarker GetMarkerFromDiagnostic(Diagnostic diagnostic) - { - DiagnosticSeverity severity = - diagnostic.Severity.GetValueOrDefault( - DiagnosticSeverity.Error); - - return new ScriptFileMarker - { - Level = MapDiagnosticSeverityToLevel(severity), - Message = diagnostic.Message, - ScriptRegion = new ScriptRegion - { - StartLineNumber = diagnostic.Range.Start.Line + 1, - StartColumnNumber = diagnostic.Range.Start.Character + 1, - EndLineNumber = diagnostic.Range.End.Line + 1, - EndColumnNumber = diagnostic.Range.End.Character + 1 - } - }; - } - - private static ScriptFileMarkerLevel MapDiagnosticSeverityToLevel(DiagnosticSeverity severity) - { - switch (severity) - { - case DiagnosticSeverity.Hint: - case DiagnosticSeverity.Information: - return ScriptFileMarkerLevel.Information; - - case DiagnosticSeverity.Warning: - return ScriptFileMarkerLevel.Warning; - - case DiagnosticSeverity.Error: - return ScriptFileMarkerLevel.Error; - - default: - return ScriptFileMarkerLevel.Error; - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs deleted file mode 100644 index 4c805a2b1..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs +++ /dev/null @@ -1,29 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class AttachRequest - { - public static readonly - RequestType Type = - RequestType.Create("attach"); - } - - public class AttachRequestArguments - { - public string ComputerName { get; set; } - - public string ProcessId { get; set; } - - public string RunspaceId { get; set; } - - public string RunspaceName { get; set; } - - public string CustomPipeName { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs deleted file mode 100644 index 0eeb00d8f..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs +++ /dev/null @@ -1,82 +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 Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class Breakpoint - { - public int? Id { get; set; } - - /// - /// Gets an boolean indicator that if true, breakpoint could be set - /// (but not necessarily at the desired location). - /// - public bool Verified { get; set; } - - /// - /// Gets an optional message about the state of the breakpoint. This is shown to the user - /// and can be used to explain why a breakpoint could not be verified. - /// - public string Message { get; set; } - - public Source Source { get; set; } - - public int? Line { get; set; } - - public int? Column { get; set; } - - private Breakpoint() - { - } - - public static Breakpoint Create( - BreakpointDetails breakpointDetails) - { - Validate.IsNotNull(nameof(breakpointDetails), breakpointDetails); - - return new Breakpoint - { - Id = breakpointDetails.Id, - Verified = breakpointDetails.Verified, - Message = breakpointDetails.Message, - Source = new Source { Path = breakpointDetails.Source }, - Line = breakpointDetails.LineNumber, - Column = breakpointDetails.ColumnNumber - }; - } - - public static Breakpoint Create( - CommandBreakpointDetails breakpointDetails) - { - Validate.IsNotNull(nameof(breakpointDetails), breakpointDetails); - - return new Breakpoint { - Verified = breakpointDetails.Verified, - Message = breakpointDetails.Message - }; - } - - public static Breakpoint Create( - SourceBreakpoint sourceBreakpoint, - string source, - string message, - bool verified = false) - { - Validate.IsNotNull(nameof(sourceBreakpoint), sourceBreakpoint); - Validate.IsNotNull(nameof(source), source); - Validate.IsNotNull(nameof(message), message); - - return new Breakpoint { - Verified = verified, - Message = message, - Source = new Source { Path = source }, - Line = sourceBreakpoint.Line, - Column = sourceBreakpoint.Column - }; - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/BreakpointEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/BreakpointEvent.cs deleted file mode 100644 index a339a70d9..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/BreakpointEvent.cs +++ /dev/null @@ -1,20 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class BreakpointEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("breakpoint"); - - public string Reason { get; set; } - - public Breakpoint Breakpoint { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/ConfigurationDoneRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/ConfigurationDoneRequest.cs deleted file mode 100644 index 11cbc4ded..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/ConfigurationDoneRequest.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class ConfigurationDoneRequest - { - public static readonly - RequestType Type = - RequestType.Create("configurationDone"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/ContinueRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/ContinueRequest.cs deleted file mode 100644 index 3f9dbc59c..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/ContinueRequest.cs +++ /dev/null @@ -1,17 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class ContinueRequest - { - public static readonly - RequestType Type = - RequestType.Create("continue"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/ContinuedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/ContinuedEvent.cs deleted file mode 100644 index af442750f..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/ContinuedEvent.cs +++ /dev/null @@ -1,21 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class ContinuedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("continued"); - - public int ThreadId { get; set; } - - public bool AllThreadsContinued { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/DisconnectRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/DisconnectRequest.cs deleted file mode 100644 index 2235206cd..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/DisconnectRequest.cs +++ /dev/null @@ -1,17 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class DisconnectRequest - { - public static readonly - RequestType Type = - RequestType.Create("disconnect"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/EvaluateRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/EvaluateRequest.cs deleted file mode 100644 index d45e7d44a..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/EvaluateRequest.cs +++ /dev/null @@ -1,53 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class EvaluateRequest - { - public static readonly - RequestType Type = - RequestType.Create("evaluate"); - } - - public class EvaluateRequestArguments - { - /// - /// The expression to evaluate. - /// - public string Expression { get; set; } - - /// - /// The context in which the evaluate request is run. Possible - /// values are 'watch' if evaluate is run in a watch or 'repl' - /// if run from the REPL console. - /// - public string Context { get; set; } - - /// - /// Evaluate the expression in the context of this stack frame. - /// If not specified, the top most frame is used. - /// - public int FrameId { get; set; } - } - - public class EvaluateResponseBody - { - /// - /// The evaluation result. - /// - public string Result { get; set; } - - /// - /// If variablesReference is > 0, the evaluate result is - /// structured and its children can be retrieved by passing - /// variablesReference to the VariablesRequest - /// - public int VariablesReference { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/ExitedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/ExitedEvent.cs deleted file mode 100644 index 9f1a0d6ce..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/ExitedEvent.cs +++ /dev/null @@ -1,22 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class ExitedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("exited"); - } - - public class ExitedEventBody - { - public int ExitCode { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/InitializeRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/InitializeRequest.cs deleted file mode 100644 index 7904759d9..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/InitializeRequest.cs +++ /dev/null @@ -1,70 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class InitializeRequest - { - public static readonly - RequestType Type = - RequestType.Create("initialize"); - } - - public class InitializeRequestArguments - { - public string AdapterId { get; set; } - - public bool LinesStartAt1 { get; set; } - - public bool ColumnsStartAt1 { get; set; } - - public string PathFormat { get; set; } - - public bool SourceMaps { get; set; } - - public string GeneratedCodeDirectory { get; set; } - } - - public class InitializeResponseBody - { - /// - /// Gets or sets a boolean value that determines whether the debug adapter - /// supports the configurationDoneRequest. - /// - public bool SupportsConfigurationDoneRequest { get; set; } - - /// - /// Gets or sets a boolean value that determines whether the debug adapter - /// supports functionBreakpoints. - /// - public bool SupportsFunctionBreakpoints { get; set; } - - /// - /// Gets or sets a boolean value that determines whether the debug adapter - /// supports conditionalBreakpoints. - /// - public bool SupportsConditionalBreakpoints { get; set; } - - /// - /// Gets or sets a boolean value that determines whether the debug adapter - /// supports breakpoints that break execution after a specified number of hits. - /// - public bool SupportsHitConditionalBreakpoints { get; set; } - - /// - /// Gets or sets a boolean value that determines whether the debug adapter - /// supports a (side effect free) evaluate request for data hovers. - /// - public bool SupportsEvaluateForHovers { get; set; } - - /// - /// Gets or sets a boolean value that determines whether the debug adapter - /// supports allowing the user to set a variable from the Variables debug windows. - /// - public bool SupportsSetVariable { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/InitializedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/InitializedEvent.cs deleted file mode 100644 index 7253b7b30..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/InitializedEvent.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class InitializedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("initialized"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/LaunchRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/LaunchRequest.cs deleted file mode 100644 index 1bf5c9ea1..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/LaunchRequest.cs +++ /dev/null @@ -1,73 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class LaunchRequest - { - public static readonly - RequestType Type = - RequestType.Create("launch"); - } - - public class LaunchRequestArguments - { - /// - /// Gets or sets the absolute path to the script to debug. - /// - public string Script { get; set; } - - /// - /// Gets or sets a boolean value that indicates whether the script should be - /// run with (false) or without (true) debugging support. - /// - public bool NoDebug { get; set; } - - /// - /// Gets or sets a boolean value that determines whether to automatically stop - /// target after launch. If not specified, target does not stop. - /// - public bool StopOnEntry { get; set; } - - /// - /// Gets or sets optional arguments passed to the debuggee. - /// - public string[] Args { get; set; } - - /// - /// Gets or sets the working directory of the launched debuggee (specified as an absolute path). - /// If omitted the debuggee is lauched in its own directory. - /// - public string Cwd { get; set; } - - /// - /// Gets or sets a boolean value that determines whether to create a temporary - /// integrated console for the debug session. Default is false. - /// - public bool CreateTemporaryIntegratedConsole { get; set; } - - /// - /// Gets or sets the absolute path to the runtime executable to be used. - /// Default is the runtime executable on the PATH. - /// - public string RuntimeExecutable { get; set; } - - /// - /// Gets or sets the optional arguments passed to the runtime executable. - /// - public string[] RuntimeArgs { get; set; } - - /// - /// Gets or sets optional environment variables to pass to the debuggee. The string valued - /// properties of the 'environmentVariables' are used as key/value pairs. - /// - public Dictionary Env { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs deleted file mode 100644 index 1a254b96f..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs +++ /dev/null @@ -1,20 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - // /** StepOver request; value of command field is "next". - // he request starts the debuggee to run again for one step. - // penDebug will respond with a StoppedEvent (event type 'step') after running the step. - public class NextRequest - { - public static readonly - RequestType Type = - RequestType.Create("next"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/OutputEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/OutputEvent.cs deleted file mode 100644 index 0044855c4..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/OutputEvent.cs +++ /dev/null @@ -1,24 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class OutputEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("output"); - } - - public class OutputEventBody - { - public string Category { get; set; } - - public string Output { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/PauseRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/PauseRequest.cs deleted file mode 100644 index 6fa67c584..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/PauseRequest.cs +++ /dev/null @@ -1,17 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class PauseRequest - { - public static readonly - RequestType Type = - RequestType.Create("pause"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/Scope.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/Scope.cs deleted file mode 100644 index 5413869f4..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/Scope.cs +++ /dev/null @@ -1,39 +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.Protocol.DebugAdapter -{ - public class Scope - { - /// - /// Gets or sets the name of the scope (as such 'Arguments', 'Locals') - /// - public string Name { get; set; } - - /// - /// Gets or sets the variables of this scope can be retrieved by passing the - /// value of variablesReference to the VariablesRequest. - /// - public int VariablesReference { get; set; } - - /// - /// Gets or sets a boolean value indicating if number of variables in - /// this scope is large or expensive to retrieve. - /// - public bool Expensive { get; set; } - - public static Scope Create(VariableScope scope) - { - return new Scope { - Name = scope.Name, - VariablesReference = scope.Id, - // Temporary fix for #95 to get debug hover tips to work well at least for the local scope. - Expensive = ((scope.Name != VariableContainerDetails.LocalScopeName) && - (scope.Name != VariableContainerDetails.AutoVariablesName)) - }; - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/ScopesRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/ScopesRequest.cs deleted file mode 100644 index 4905ffdfe..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/ScopesRequest.cs +++ /dev/null @@ -1,29 +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.Diagnostics; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class ScopesRequest - { - public static readonly - RequestType Type = - RequestType.Create("scopes"); - } - - [DebuggerDisplay("FrameId = {FrameId}")] - public class ScopesRequestArguments - { - public int FrameId { get; set; } - } - - public class ScopesResponseBody - { - public Scope[] Scopes { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs deleted file mode 100644 index 82d41ac53..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs +++ /dev/null @@ -1,45 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - /// - /// SetBreakpoints request; value of command field is "setBreakpoints". - /// Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. - /// To clear all breakpoint for a source, specify an empty array. - /// When a breakpoint is hit, a StoppedEvent (event type 'breakpoint') is generated. - /// - public class SetBreakpointsRequest - { - public static readonly - RequestType Type = - RequestType.Create("setBreakpoints"); - } - - public class SetBreakpointsRequestArguments - { - public Source Source { get; set; } - - public SourceBreakpoint[] Breakpoints { get; set; } - } - - public class SourceBreakpoint - { - public int Line { get; set; } - - public int? Column { get; set; } - - public string Condition { get; set; } - - public string HitCondition { get; set; } - } - - public class SetBreakpointsResponseBody - { - public Breakpoint[] Breakpoints { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs deleted file mode 100644 index 6a7313324..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs +++ /dev/null @@ -1,31 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - /// - /// SetExceptionBreakpoints request; value of command field is "setExceptionBreakpoints". - /// Enable that the debuggee stops on exceptions with a StoppedEvent (event type 'exception'). - /// - public class SetExceptionBreakpointsRequest - { - public static readonly - RequestType Type = - RequestType.Create("setExceptionBreakpoints"); - } - - /// - /// Arguments for "setExceptionBreakpoints" request. - /// - public class SetExceptionBreakpointsRequestArguments - { - /// - /// Gets or sets the names of enabled exception breakpoints. - /// - public string[] Filters { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetFunctionBreakpointsRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetFunctionBreakpointsRequest.cs deleted file mode 100644 index 6fb951553..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetFunctionBreakpointsRequest.cs +++ /dev/null @@ -1,33 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class SetFunctionBreakpointsRequest - { - public static readonly - RequestType Type = - RequestType.Create("setFunctionBreakpoints"); - } - - public class SetFunctionBreakpointsRequestArguments - { - public FunctionBreakpoint[] Breakpoints { get; set; } - } - - public class FunctionBreakpoint - { - /// - /// Gets or sets the name of the function to break on when it is invoked. - /// - public string Name { get; set; } - - public string Condition { get; set; } - - public string HitCondition { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetVariableRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetVariableRequest.cs deleted file mode 100644 index 47e41cb36..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetVariableRequest.cs +++ /dev/null @@ -1,36 +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.Diagnostics; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - /// - /// SetVariable request; value of command field is "setVariable". - /// Request is initiated when user uses the debugger Variables UI to change the value of a variable. - /// - public class SetVariableRequest - { - public static readonly - RequestType Type = - RequestType.Create("setVariable"); - } - - [DebuggerDisplay("VariablesReference = {VariablesReference}")] - public class SetVariableRequestArguments - { - public int VariablesReference { get; set; } - - public string Name { get; set; } - - public string Value { get; set; } - } - - public class SetVariableResponseBody - { - public string Value { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/Source.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/Source.cs deleted file mode 100644 index 29f21bdf9..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/Source.cs +++ /dev/null @@ -1,44 +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.Protocol.DebugAdapter -{ - public class Source - { - public string Name { get; set; } - - public string Path { get; set; } - - public int? SourceReference { get; set; } - - /// - /// Gets an optional hint for how to present the source in the UI. A value of 'deemphasize' - /// can be used to indicate that the source is not available or that it is skipped on stepping. - /// - public string PresentationHint { get; set; } - } - - /// - /// An optional hint for how to present source in the UI. - /// - public enum SourcePresentationHint - { - /// - /// Dispays the source normally. - /// - Normal, - - /// - /// Display the source emphasized. - /// - Emphasize, - - /// - /// Display the source deemphasized. - /// - Deemphasize - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SourceRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SourceRequest.cs deleted file mode 100644 index 48a2b0a17..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SourceRequest.cs +++ /dev/null @@ -1,29 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class SourceRequest - { - public static readonly - RequestType Type = - RequestType.Create("source"); - } - - public class SourceRequestArguments - { - /// - /// Gets or sets the reference to the source. This is the value received in Source.reference. - /// - public int SourceReference { get; set; } - } - - public class SourceResponseBody - { - public string Content { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs deleted file mode 100644 index 4c8d80d81..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs +++ /dev/null @@ -1,112 +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.Protocol.DebugAdapter -{ - public class StackFrame - { - /// - /// Gets or sets an identifier for the stack frame. It must be unique across all threads. - /// This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or - /// to restart the execution of a stackframe. */ - /// - public int Id { get; set; } - - /// - /// Gets or sets the name of the stack frame, typically a method name - /// - public string Name { get; set; } - - /// - /// Gets or sets the optional source of the frame. - /// - public Source Source { get; set; } - - /// - /// Gets or sets line within the file of the frame. If source is null or doesn't exist, - /// line is 0 and must be ignored. - /// - public int Line { get; set; } - - /// - /// Gets or sets an optional end line of the range covered by the stack frame. - /// - public int? EndLine { get; set; } - - /// - /// Gets or sets the column within the line. If source is null or doesn't exist, - /// column is 0 and must be ignored. - /// - public int Column { get; set; } - - /// - /// Gets or sets an optional end column of the range covered by the stack frame. - /// - public int? EndColumn { get; set; } - - /// - /// Gets an optional hint for how to present this frame in the UI. A value of 'label' - /// can be used to indicate that the frame is an artificial frame that is used as a - /// visual label or separator. A value of 'subtle' can be used to change the appearance - /// of a frame in a 'subtle' way. - /// - public string PresentationHint { get; private set; } - - public static StackFrame Create( - StackFrameDetails stackFrame, - int id) - { - var sourcePresentationHint = - stackFrame.IsExternalCode ? SourcePresentationHint.Deemphasize : SourcePresentationHint.Normal; - - // When debugging an interactive session, the ScriptPath is which is not a valid source file. - // We need to make sure the user can't open the file associated with this stack frame. - // It will generate a VSCode error in this case. - Source source = null; - if (!stackFrame.ScriptPath.Contains("<")) - { - source = new Source - { - Path = stackFrame.ScriptPath, - PresentationHint = sourcePresentationHint.ToString().ToLower() - }; - } - - return new StackFrame - { - Id = id, - Name = (source != null) ? stackFrame.FunctionName : "Interactive Session", - Line = (source != null) ? stackFrame.StartLineNumber : 0, - EndLine = stackFrame.EndLineNumber, - Column = (source != null) ? stackFrame.StartColumnNumber : 0, - EndColumn = stackFrame.EndColumnNumber, - Source = source - }; - } - } - - /// - /// An optional hint for how to present a stack frame in the UI. - /// - public enum StackFramePresentationHint - { - /// - /// Dispays the stack frame as a normal stack frame. - /// - Normal, - - /// - /// Used to label an entry in the call stack that doesn't actually correspond to a stack frame. - /// This is typically used to label transitions to/from "external" code. - /// - Label, - - /// - /// Displays the stack frame in a subtle way, typically used from loctaions outside of the current project or workspace. - /// - Subtle - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StackTraceRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StackTraceRequest.cs deleted file mode 100644 index 56a88a950..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StackTraceRequest.cs +++ /dev/null @@ -1,55 +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.Diagnostics; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class StackTraceRequest - { - public static readonly - RequestType Type = - RequestType.Create("stackTrace"); - } - - [DebuggerDisplay("ThreadId = {ThreadId}, Levels = {Levels}")] - public class StackTraceRequestArguments - { - /// - /// Gets or sets the ThreadId of this stacktrace. - /// - public int ThreadId { get; set; } - - /// - /// Gets or sets the index of the first frame to return. If omitted frames start at 0. - /// - public int? StartFrame { get; set; } - - /// - /// Gets or sets the maximum number of frames to return. If levels is not specified or 0, all frames are returned. - /// - public int? Levels { get; set; } - - /// - /// Gets or sets the format string that specifies details on how to format the stack frames. - /// - public string Format { get; set; } - } - - public class StackTraceResponseBody - { - /// - /// Gets the frames of the stackframe. If the array has length zero, there are no stackframes available. - /// This means that there is no location information available. - /// - public StackFrame[] StackFrames { get; set; } - - /// - /// Gets the total number of frames available. - /// - public int? TotalFrames { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StartedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StartedEvent.cs deleted file mode 100644 index 5b32d4a84..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StartedEvent.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class StartedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("started"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StepInRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StepInRequest.cs deleted file mode 100644 index ec825ebbd..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StepInRequest.cs +++ /dev/null @@ -1,17 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class StepInRequest - { - public static readonly - RequestType Type = - RequestType.Create("stepIn"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StepOutRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StepOutRequest.cs deleted file mode 100644 index be0a31807..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StepOutRequest.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class StepOutRequest - { - public static readonly - RequestType Type = - RequestType.Create("stepOut"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs deleted file mode 100644 index a3c2a7921..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs +++ /dev/null @@ -1,37 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class StoppedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("stopped"); - } - - public class StoppedEventBody - { - /// - /// A value such as "step", "breakpoint", "exception", or "pause" - /// - public string Reason { get; set; } - - /// - /// Gets or sets the current thread ID, if any. - /// - public int? ThreadId { get; set; } - - public Source Source { get; set; } - - /// - /// Gets or sets additional information such as an error message. - /// - public string Text { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/TerminatedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/TerminatedEvent.cs deleted file mode 100644 index 514d0bcae..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/TerminatedEvent.cs +++ /dev/null @@ -1,19 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class TerminatedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("terminated"); - - public bool Restart { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/Thread.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/Thread.cs deleted file mode 100644 index 35a8a139d..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/Thread.cs +++ /dev/null @@ -1,15 +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.Protocol.DebugAdapter -{ - public class Thread - { - public int Id { get; set; } - - public string Name { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/ThreadsRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/ThreadsRequest.cs deleted file mode 100644 index 24432d671..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/ThreadsRequest.cs +++ /dev/null @@ -1,22 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class ThreadsRequest - { - public static readonly - RequestType Type = - RequestType.Create("threads"); - } - - public class ThreadsResponseBody - { - public Thread[] Threads { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/Variable.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/Variable.cs deleted file mode 100644 index a335b0975..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/Variable.cs +++ /dev/null @@ -1,43 +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.Protocol.DebugAdapter -{ - public class Variable - { - public string Name { get; set; } - - // /** The variable's value. For structured objects this can be a multi line text, e.g. for a function the body of a function. */ - public string Value { get; set; } - - /// - /// Gets or sets the type of the variable's value. Typically shown in the UI when hovering over the value. - /// - public string Type { get; set; } - - /// - /// Gets or sets the evaluatable name for the variable that will be evaluated by the debugger. - /// - public string EvaluateName { get; set; } - - // /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ - public int VariablesReference { get; set; } - - public static Variable Create(VariableDetailsBase variable) - { - return new Variable - { - Name = variable.Name, - Value = variable.ValueString ?? string.Empty, - Type = variable.Type, - EvaluateName = variable.Name, - VariablesReference = - variable.IsExpandable ? - variable.Id : 0 - }; - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/VariablesRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/VariablesRequest.cs deleted file mode 100644 index f387c500f..000000000 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/VariablesRequest.cs +++ /dev/null @@ -1,29 +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.Diagnostics; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter -{ - public class VariablesRequest - { - public static readonly - RequestType Type = - RequestType.Create("variables"); - } - - [DebuggerDisplay("VariablesReference = {VariablesReference}")] - public class VariablesRequestArguments - { - public int VariablesReference { get; set; } - } - - public class VariablesResponseBody - { - public Variable[] Variables { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ClientCapabilities.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ClientCapabilities.cs deleted file mode 100644 index f66982c43..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ClientCapabilities.cs +++ /dev/null @@ -1,26 +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.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Defines a class that describes the capabilities of a language - /// client. At this time no specific capabilities are listed for - /// clients. - /// - public class ClientCapabilities - { - public WorkspaceClientCapabilities Workspace { get; set; } - public TextDocumentClientCapabilities TextDocument { get; set; } - public object Experimental { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/CodeAction.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/CodeAction.cs deleted file mode 100644 index a382d8dcf..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/CodeAction.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Newtonsoft.Json.Linq; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class CodeActionRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/codeAction"); - } - - /// - /// Parameters for CodeActionRequest. - /// - public class CodeActionParams - { - /// - /// The document in which the command was invoked. - /// - public TextDocumentIdentifier TextDocument { get; set; } - - /// - /// The range for which the command was invoked. - /// - public Range Range { get; set; } - - /// - /// Context carrying additional information. - /// - public CodeActionContext Context { get; set; } - } - - public class CodeActionContext - { - public Diagnostic[] Diagnostics { get; set; } - } - - public class CodeActionCommand - { - public string Title { get; set; } - - public string Command { get; set; } - - public JArray Arguments { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/CodeLens.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/CodeLens.cs deleted file mode 100644 index fd12f284d..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/CodeLens.cs +++ /dev/null @@ -1,56 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Newtonsoft.Json.Linq; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Code Lens options. - /// - public class CodeLensOptions - { - /// - /// Code lens has a resolve provider as well. - /// - public bool ResolveProvider { get; set; } - } - - public class CodeLens - { - public Range Range { get; set; } - - public ServerCommand Command { get; set; } - - public JToken Data { get; set; } - } - - /// - /// A code lens represents a command that should be shown along with - /// source text, like the number of references, a way to run tests, etc. - /// - /// A code lens is _unresolved_ when no command is associated to it. For performance - /// reasons the creation of a code lens and resolving should be done in two stages. - /// - public class CodeLensRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/codeLens"); - - /// - /// The document to request code lens for. - /// - public TextDocumentIdentifier TextDocument { get; set; } - } - - public class CodeLensResolveRequest - { - public static readonly - RequestType Type = - RequestType.Create("codeLens/resolve"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/CommentHelpRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/CommentHelpRequest.cs deleted file mode 100644 index d4406519f..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/CommentHelpRequest.cs +++ /dev/null @@ -1,28 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - class CommentHelpRequest - { - public static readonly RequestType Type - = RequestType.Create("powerShell/getCommentHelp"); - } - - public class CommentHelpRequestResult - { - public string[] Content { get; set; } - } - - public class CommentHelpRequestParams - { - public string DocumentUri { get; set; } - public Position TriggerPosition { get; set; } - public bool BlockComment { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Completion.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Completion.cs deleted file mode 100644 index 2e0cbeeca..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Completion.cs +++ /dev/null @@ -1,144 +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.Diagnostics; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class CompletionRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/completion"); - } - - public class CompletionResolveRequest - { - public static readonly - RequestType Type = - RequestType.Create("completionItem/resolve"); - } - - /// - /// Completion registration options. - /// - public class CompletionRegistrationOptions : TextDocumentRegistrationOptions - { - // We duplicate the properties of completionOptions class here because - // we cannot derive from two classes. One way to get around this situation - // is to use define CompletionOptions as an interface instead of a class. - public bool? ResolveProvider { get; set; } - - public string[] TriggerCharacters { get; set; } - } - - public enum CompletionItemKind - { - Text = 1, - Method = 2, - Function = 3, - Constructor = 4, - Field = 5, - Variable = 6, - Class = 7, - Interface = 8, - Module = 9, - Property = 10, - Unit = 11, - Value = 12, - Enum = 13, - Keyword = 14, - Snippet = 15, - Color = 16, - File = 17, - Reference = 18, - Folder = 19 - } - - public enum InsertTextFormat - { - PlainText = 1, - Snippet = 2, - } - - [DebuggerDisplay("NewText = {NewText}, Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}")] - public class TextEdit - { - public Range Range { get; set; } - - public string NewText { get; set; } - } - - [DebuggerDisplay("Kind = {Kind.ToString()}, Label = {Label}, Detail = {Detail}")] - public class CompletionItem - { - public string Label { get; set; } - - public CompletionItemKind? Kind { get; set; } - - public string Detail { get; set; } - - /// - /// Gets or sets the documentation string for the completion item. - /// - public string Documentation { get; set; } - - public string SortText { get; set; } - - public string FilterText { get; set; } - - public string InsertText { get; set; } - - public InsertTextFormat InsertTextFormat { get; set; } = InsertTextFormat.PlainText; - - public Range Range { get; set; } - - public string[] CommitCharacters { get; set; } - - public TextEdit TextEdit { get; set; } - - public TextEdit[] AdditionalTextEdits { get; set; } - - public CommandType Command { get; set; } - - /// - /// Gets or sets a custom data field that allows the server to mark - /// each completion item with an identifier that will help correlate - /// the item to the previous completion request during a completion - /// resolve request. - /// - public object Data { get; set; } - } - - /// - /// Represents a reference to a command. Provides a title which will be used to - /// represent a command in the UI and, optionally, an array of arguments which - /// will be passed to the command handler function when invoked. - /// - /// The name of the corresponding type in vscode-languageserver-node is Command - /// but since .net does not allow a property name (Command) and its enclosing - /// type name to be the same we change its name to CommandType. - /// - public class CommandType - { - /// - /// Title of the command. - /// - /// - public string Title { get; set; } - - /// - /// The identifier of the actual command handler. - /// - public string Command { get; set; } - - /// - /// Arguments that the command handler should be invoked with. - /// - public object[] Arguments { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Configuration.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Configuration.cs deleted file mode 100644 index a4228ee19..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Configuration.cs +++ /dev/null @@ -1,21 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class DidChangeConfigurationNotification - { - public static readonly - NotificationType, object> Type = - NotificationType, object>.Create("workspace/didChangeConfiguration"); - } - - public class DidChangeConfigurationParams - { - public TConfig Settings { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Definition.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Definition.cs deleted file mode 100644 index cee9a7215..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Definition.cs +++ /dev/null @@ -1,17 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class DefinitionRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/definition"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Diagnostics.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Diagnostics.cs deleted file mode 100644 index e5e46d45f..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Diagnostics.cs +++ /dev/null @@ -1,81 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class PublishDiagnosticsNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("textDocument/publishDiagnostics"); - - /// - /// Gets or sets the URI for which diagnostic information is reported. - /// - public string Uri { get; set; } - - /// - /// Gets or sets the array of diagnostic information items. - /// - public Diagnostic[] Diagnostics { get; set; } - } - - public enum DiagnosticSeverity - { - /// - /// Indicates that the diagnostic represents an error. - /// - Error = 1, - - /// - /// Indicates that the diagnostic represents a warning. - /// - Warning = 2, - - /// - /// Indicates that the diagnostic represents an informational message. - /// - Information = 3, - - /// - /// Indicates that the diagnostic represents a hint. - /// - Hint = 4 - } - - public class Diagnostic - { - public Range Range { get; set; } - - /// - /// Gets or sets the severity of the diagnostic. If omitted, the - /// client should interpret the severity. - /// - public DiagnosticSeverity? Severity { get; set; } - - /// - /// Gets or sets the diagnostic's code (optional). - /// - public string Code { get; set; } - - /// - /// Gets or sets the diagnostic message. - /// - public string Message { get; set; } - - /// - /// Gets or sets the source of the diagnostic message. - /// - public string Source { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/DocumentHighlight.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/DocumentHighlight.cs deleted file mode 100644 index cf8196f18..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/DocumentHighlight.cs +++ /dev/null @@ -1,31 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public enum DocumentHighlightKind - { - Text = 1, - Read = 2, - Write = 3 - } - - public class DocumentHighlight - { - public Range Range { get; set; } - - public DocumentHighlightKind Kind { get; set; } - } - - public class DocumentHighlightRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/documentHighlight"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/DynamicRegistrationCapability.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/DynamicRegistrationCapability.cs deleted file mode 100644 index 64d2c35ce..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/DynamicRegistrationCapability.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Class to represent if a capability supports dynamic registration. - /// - public class DynamicRegistrationCapability - { - /// - /// Whether the capability supports dynamic registration. - /// - public bool? DynamicRegistration { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs deleted file mode 100644 index a7c385bc6..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs +++ /dev/null @@ -1,185 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class ExtensionCommandAddedNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("powerShell/extensionCommandAdded"); - - public string Name { get; set; } - - public string DisplayName { get; set; } - } - - public class ExtensionCommandUpdatedNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("powerShell/extensionCommandUpdated"); - - public string Name { get; set; } - } - - public class ExtensionCommandRemovedNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("powerShell/extensionCommandRemoved"); - - public string Name { 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; } - - } - - public class InvokeExtensionCommandRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/invokeExtensionCommand"); - - public string Name { get; set; } - - public ClientEditorContext Context { get; set; } - } - - public class GetEditorContextRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/getEditorContext"); - } - - public enum EditorCommandResponse - { - Unsupported, - OK - } - - public class InsertTextRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/insertText"); - - public string FilePath { get; set; } - - public string InsertText { get; set; } - - public Range InsertRange { get; set; } - } - - public class SetSelectionRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/setSelection"); - - public Range SelectionRange { get; set; } - } - - public class SetCursorPositionRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/setCursorPosition"); - - public Position CursorPosition { get; set; } - } - - public class NewFileRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/newFile"); - } - - public class OpenFileRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/openFile"); - } - - public class OpenFileDetails - { - public string FilePath { get; set; } - - public bool Preview { get; set; } - } - - public class CloseFileRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/closeFile"); - } - - public class SaveFileRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/saveFile"); - } - - public class SaveFileDetails - { - public string FilePath { get; set; } - - public string NewPath { get; set; } - } - - public class ShowInformationMessageRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/showInformationMessage"); - } - - public class ShowWarningMessageRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/showWarningMessage"); - } - - public class ShowErrorMessageRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/showErrorMessage"); - } - - public class SetStatusBarMessageRequest - { - public static readonly - RequestType Type = - RequestType.Create("editor/setStatusBarMessage"); - } - - public class StatusBarMessageDetails - { - public string Message { get; set; } - - public int? Timeout { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ExecutionStatusChangedEvent.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ExecutionStatusChangedEvent.cs deleted file mode 100644 index 5ac6eac6d..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ExecutionStatusChangedEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Defines an event type for PowerShell context execution status changes (e.g. execution has completed) - /// - public class ExecutionStatusChangedEvent - { - /// - /// The notification type for execution status change events in the message protocol - /// - public static readonly - NotificationType Type = - NotificationType.Create("powerShell/executionStatusChanged"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ExpandAliasRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ExpandAliasRequest.cs deleted file mode 100644 index d5c2a9bc9..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ExpandAliasRequest.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class ExpandAliasRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/expandAlias"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/FindModuleRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/FindModuleRequest.cs deleted file mode 100644 index fe14fdb65..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/FindModuleRequest.cs +++ /dev/null @@ -1,24 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using System.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class FindModuleRequest - { - public static readonly - RequestType, object, object, object> Type = - RequestType, object, object, object>.Create("powerShell/findModule"); - } - - - public class PSModuleMessage - { - public string Name { get; set; } - public string Description { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Folding.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Folding.cs deleted file mode 100644 index f7b1d8f01..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Folding.cs +++ /dev/null @@ -1,69 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class FoldingRangeRequest - { - /// - /// A request to provide folding ranges in a document. The request's - /// parameter is of type [FoldingRangeParams](#FoldingRangeParams), the - /// response is of type [FoldingRangeList](#FoldingRangeList) or a Thenable - /// that resolves to such. - /// Ref: https://github.com/Microsoft/vscode-languageserver-node/blob/5350bc2ffe8afb17357c1a66fbdd3845fa05adfd/protocol/src/protocol.foldingRange.ts#L112-L120 - /// - public static readonly - RequestType Type = - RequestType.Create("textDocument/foldingRange"); - } - - /// - /// Parameters for a [FoldingRangeRequest](#FoldingRangeRequest). - /// Ref: https://github.com/Microsoft/vscode-languageserver-node/blob/5350bc2ffe8afb17357c1a66fbdd3845fa05adfd/protocol/src/protocol.foldingRange.ts#L102-L110 - /// - public class FoldingRangeParams - { - /// - /// The text document - /// - public TextDocumentIdentifier TextDocument { get; set; } - } - - /// - /// Represents a folding range. - /// Ref: https://github.com/Microsoft/vscode-languageserver-node/blob/5350bc2ffe8afb17357c1a66fbdd3845fa05adfd/protocol/src/protocol.foldingRange.ts#L69-L100 - /// - public class FoldingRange - { - /// - /// The zero-based line number from where the folded range starts. - /// - public int StartLine { get; set; } - - /// - /// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. - /// - public int StartCharacter { get; set; } - - /// - /// The zero-based line number where the folded range ends. - /// - public int EndLine { get; set; } - - /// - /// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. - /// - public int EndCharacter { get; set; } - - /// - /// Describes the kind of the folding range such as `comment' or 'region'. The kind - /// is used to categorize folding ranges and used by commands like 'Fold all comments'. See - /// [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds. - /// - public string Kind { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Formatting.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Formatting.cs deleted file mode 100644 index f063ba163..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Formatting.cs +++ /dev/null @@ -1,95 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class DocumentFormattingRequest - { - public static readonly RequestType Type = RequestType.Create("textDocument/formatting"); - } - - public class DocumentRangeFormattingRequest - { - public static readonly RequestType Type = RequestType.Create("textDocument/rangeFormatting"); - - } - - public class DocumentOnTypeFormattingRequest - { - public static readonly RequestType Type = RequestType.Create("textDocument/onTypeFormatting"); - - } - - public class DocumentRangeFormattingParams - { - /// - /// The document to format. - /// - public TextDocumentIdentifier TextDocument { get; set; } - - /// - /// The range to format. - /// - /// - public Range Range { get; set; } - - /// - /// The format options. - /// - public FormattingOptions Options { get; set; } - } - - public class DocumentOnTypeFormattingParams - { - /// - /// The document to format. - /// - public TextDocumentIdentifier TextDocument { get; set; } - - /// - /// The position at which this request was sent. - /// - public Position Position { get; set; } - - /// - /// The character that has been typed. - /// - public string ch { get; set; } - - /// - /// The format options. - /// - public FormattingOptions options { get; set; } - } - - public class DocumentFormattingParams - { - /// - /// The document to format. - /// - public TextDocumentIdentifier TextDocument { get; set; } - - /// - /// The format options. - /// - public FormattingOptions options { get; set; } - } - - public class FormattingOptions - { - /// - /// Size of a tab in spaces. - /// - public int TabSize { get; set; } - - /// - /// Prefer spaces over tabs. - /// - public bool InsertSpaces { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/GetCommandRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/GetCommandRequest.cs deleted file mode 100644 index df847dcbe..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/GetCommandRequest.cs +++ /dev/null @@ -1,34 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Describes the request to get the details for PowerShell Commands from the current session. - /// - public class GetCommandRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getCommand"); - } - - /// - /// Describes the message to get the details for a single PowerShell Command - /// from the current session - /// - public class PSCommandMessage - { - public string Name { get; set; } - public string ModuleName { get; set; } - public string DefaultParameterSet { get; set; } - public Dictionary Parameters { get; set; } - public System.Collections.ObjectModel.ReadOnlyCollection ParameterSets { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/GetPSHostProcessesRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/GetPSHostProcessesRequest.cs deleted file mode 100644 index 3c33a61d3..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/GetPSHostProcessesRequest.cs +++ /dev/null @@ -1,27 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class GetPSHostProcessesRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getPSHostProcesses"); - } - - public class GetPSHostProcessesResponse - { - public string ProcessName { get; set; } - - public int ProcessId { get; set; } - - public string AppDomainName { get; set; } - - public string MainWindowTitle { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/GetPSSARulesRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/GetPSSARulesRequest.cs deleted file mode 100644 index 2199558c5..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/GetPSSARulesRequest.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - class GetPSSARulesRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getPSSARules"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/GetRunspaceRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/GetRunspaceRequest.cs deleted file mode 100644 index e151aa7f0..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/GetRunspaceRequest.cs +++ /dev/null @@ -1,25 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class GetRunspaceRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getRunspace"); - } - - public class GetRunspaceResponse - { - public int Id { get; set; } - - public string Name { get; set; } - - public string Availability { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Hover.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Hover.cs deleted file mode 100644 index 42c853254..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Hover.cs +++ /dev/null @@ -1,37 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class MarkedString - { - public string Language { get; set; } - - public string Value { get; set; } - } - - public class Hover - { - public MarkedString[] Contents { get; set; } - - public Range Range { get; set; } - } - - public class HoverRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/hover"); - - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Initialize.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Initialize.cs deleted file mode 100644 index 234b02169..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Initialize.cs +++ /dev/null @@ -1,79 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class InitializeRequest - { - public static readonly - RequestType Type = - RequestType.Create("initialize"); - } - - public enum TraceType { - Off, - Messages, - Verbose - } - - public class InitializeParams { - /// - /// The process Id of the parent process that started the server - /// - public int ProcessId { get; set; } - - /// - /// The root path of the workspace. It is null if no folder is open. - /// - /// This property has been deprecated in favor of RootUri. - /// - public string RootPath { get; set; } - - /// - /// The root uri of the workspace. It is null if not folder is open. If both - /// `RootUri` and `RootPath` are non-null, `RootUri` should be used. - /// - public string RootUri { get; set; } - - /// - /// The capabilities provided by the client. - /// - public ClientCapabilities Capabilities { get; set; } - - /// - /// User provided initialization options. - /// - /// This is defined as `any` type on the client side. - /// - public object InitializationOptions { get; set; } - - // TODO We need to verify if the deserializer will map the type defined in the client - // to an enum. - /// - /// The initial trace setting. If omitted trace is disabled. - /// - public TraceType Trace { get; set; } = TraceType.Off; - } - - public class InitializeResult - { - /// - /// Gets or sets the capabilities provided by the language server. - /// - public ServerCapabilities Capabilities { get; set; } - } - - public class InitializeError - { - /// - /// Gets or sets a boolean indicating whether the client should retry - /// sending the Initialize request after showing the error to the user. - /// - public bool Retry { get; set;} - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/InitializedNotification.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/InitializedNotification.cs deleted file mode 100644 index 90836017f..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/InitializedNotification.cs +++ /dev/null @@ -1,29 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// The initialized notification is sent from the client to the server after the client received the result - /// of the initialize request but before the client is sending any other request or notification to the server. - /// The server can use the initialized notification for example to dynamically register capabilities. - /// The initialized notification may only be sent once. - /// - public class InitializedNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("initialized"); - } - - /// - /// Currently, the initialized message has no parameters. - /// - public class InitializedParams - { - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/InstallModuleRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/InstallModuleRequest.cs deleted file mode 100644 index 096d0e461..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/InstallModuleRequest.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - class InstallModuleRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/installModule"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/PowerShellVersionRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/PowerShellVersionRequest.cs deleted file mode 100644 index 8f16b0abb..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/PowerShellVersionRequest.cs +++ /dev/null @@ -1,52 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Session; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class PowerShellVersionRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getVersion"); - } - - public class PowerShellVersion - { - public string Version { get; set; } - - public string DisplayVersion { get; set; } - - public string Edition { get; set; } - - public string Architecture { get; set; } - - public PowerShellVersion() - { - } - - public PowerShellVersion(PowerShellVersionDetails versionDetails) - { - this.Version = versionDetails.VersionString; - this.DisplayVersion = $"{versionDetails.Version.Major}.{versionDetails.Version.Minor}"; - this.Edition = versionDetails.Edition; - - switch (versionDetails.Architecture) - { - case PowerShellProcessArchitecture.X64: - this.Architecture = "x64"; - break; - case PowerShellProcessArchitecture.X86: - this.Architecture = "x86"; - break; - default: - this.Architecture = "Architecture Unknown"; - break; - } - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ProjectTemplate.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ProjectTemplate.cs deleted file mode 100644 index 1b7441b28..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ProjectTemplate.cs +++ /dev/null @@ -1,42 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Templates; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class NewProjectFromTemplateRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/newProjectFromTemplate"); - - public string DestinationPath { get; set; } - - public string TemplatePath { get; set; } - } - - public class NewProjectFromTemplateResponse - { - public bool CreationSuccessful { get; set; } - } - - public class GetProjectTemplatesRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getProjectTemplates"); - - public bool IncludeInstalledModules { get; set; } - } - - public class GetProjectTemplatesResponse - { - public bool NeedsModuleInstall { get; set; } - - public TemplateDetails[] Templates { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/References.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/References.cs deleted file mode 100644 index 6965cb073..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/References.cs +++ /dev/null @@ -1,27 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class ReferencesRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/references"); - } - - public class ReferencesParams : TextDocumentPositionParams - { - public ReferencesContext Context { get; set; } - } - - public class ReferencesContext - { - public bool IncludeDeclaration { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/RunspaceChanged.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/RunspaceChanged.cs deleted file mode 100644 index 3f2448097..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/RunspaceChanged.cs +++ /dev/null @@ -1,37 +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 Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class RunspaceChangedEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("powerShell/runspaceChanged"); - } - - public class RunspaceDetails - { - public PowerShellVersion PowerShellVersion { get; set; } - - public RunspaceLocation RunspaceType { get; set; } - - public string ConnectionString { get; set; } - - public RunspaceDetails() - { - } - - public RunspaceDetails(Session.RunspaceDetails eventArgs) - { - this.PowerShellVersion = new PowerShellVersion(eventArgs.PowerShellVersion); - this.RunspaceType = eventArgs.Location; - this.ConnectionString = eventArgs.ConnectionString; - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ScriptRegionRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ScriptRegionRequest.cs deleted file mode 100644 index 79b36161e..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ScriptRegionRequest.cs +++ /dev/null @@ -1,57 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Class to encapsulate the request type. - /// - class ScriptRegionRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getScriptRegion"); - } - - /// - /// Class to encapsulate the request parameters. - /// - class ScriptRegionRequestParams - { - /// - /// Path of the file for which the formatting region is requested. - /// - public string FileUri; - - /// - /// Hint character. - /// - public string Character; - - /// - /// 1-based line number of the character. - /// - public int Line; - - /// - /// 1-based column number of the character. - /// - public int Column; - } - - /// - /// Class to encapsulate the result of ScriptRegionRequest. - /// - class ScriptRegionRequestResult - { - /// - /// A region in the script that encapsulates the given character/position which is suitable - /// for formatting - /// - public ScriptRegion scriptRegion; - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ServerCapabilities.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ServerCapabilities.cs deleted file mode 100644 index e53ca4f6c..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ServerCapabilities.cs +++ /dev/null @@ -1,121 +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.Protocol.LanguageServer -{ - public class ServerCapabilities - { - public TextDocumentSyncKind? TextDocumentSync { get; set; } - - public bool? HoverProvider { get; set; } - - public CompletionOptions CompletionProvider { get; set; } - - public SignatureHelpOptions SignatureHelpProvider { get; set; } - - public bool? DefinitionProvider { get; set; } - - public bool? ReferencesProvider { get; set; } - - public bool? DocumentHighlightProvider { get; set; } - - public bool? DocumentSymbolProvider { get; set; } - - public bool? WorkspaceSymbolProvider { get; set; } - - public bool? CodeActionProvider { get; set; } - - public CodeLensOptions CodeLensProvider { get; set; } - - public bool? DocumentFormattingProvider { get; set; } - - public bool? DocumentRangeFormattingProvider { get; set; } - - public DocumentOnTypeFormattingOptions DocumentOnTypeFormattingProvider { get; set; } - - public bool? RenameProvider { get; set; } - - public DocumentLinkOptions DocumentLinkProvider { get; set; } - - public ExecuteCommandOptions ExecuteCommandProvider { get; set; } - - public object Experimental { get; set; } - - public bool FoldingRangeProvider { get; set; } = false; - } - - /// - /// Execute command options. - /// - public class ExecuteCommandOptions - { - /// - /// The commands to be executed on the server. - /// - public string[] Commands { get; set; } - } - - /// - /// Document link options. - /// - public class DocumentLinkOptions - { - /// - /// Document links have a resolve provider. - /// - public bool? ResolveProvider { get; set; } - } - - /// - /// Options that the server provides for OnTypeFormatting request. - /// - public class DocumentOnTypeFormattingOptions - { - /// - /// A character on which formatting should be triggered. - /// - public string FirstTriggerCharacter { get; set; } - - /// - /// More trigger characters. - /// - public string[] MoreTriggerCharacters { get; set; } - } - - /// - /// Defines the document synchronization strategies that a server may support. - /// - public enum TextDocumentSyncKind - { - /// - /// Indicates that documents should not be synced at all. - /// - None = 0, - - /// - /// Indicates that document changes are always sent with the full content. - /// - Full, - - /// - /// Indicates that document changes are sent as incremental changes after - /// the initial document content has been sent. - /// - Incremental - } - - public class CompletionOptions - { - public bool? ResolveProvider { get; set; } - - public string[] TriggerCharacters { get; set; } - } - - public class SignatureHelpOptions - { - public string[] TriggerCharacters { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ServerCommand.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ServerCommand.cs deleted file mode 100644 index b299b5606..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ServerCommand.cs +++ /dev/null @@ -1,28 +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 Newtonsoft.Json.Linq; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class ServerCommand - { - /// - /// Title of the command, like `save`. - /// - public string Title { get; set; } - - /// - /// The identifier of the actual command handler. - /// - public string Command { get; set; } - - /// - /// Arguments that the command handler should be - /// invoked with. - /// - public JArray Arguments { get; set; } - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/SetPSSARulesRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/SetPSSARulesRequest.cs deleted file mode 100644 index e3298cbd8..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/SetPSSARulesRequest.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - class SetPSSARulesRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/setPSSARules"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/ShowHelpRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/ShowHelpRequest.cs deleted file mode 100644 index 0d73074d2..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/ShowHelpRequest.cs +++ /dev/null @@ -1,18 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - - public class ShowHelpRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/showHelp"); - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/Shutdown.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/Shutdown.cs deleted file mode 100644 index f5fffce1c..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/Shutdown.cs +++ /dev/null @@ -1,32 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - /// - /// Defines a message that is sent from the client to request - /// that the server shut down. - /// - public class ShutdownRequest - { - public static readonly - RequestType0 Type = - RequestType0.Create("shutdown"); - } - - /// - /// Defines an event that is sent from the client to notify that - /// the client is exiting and the server should as well. - /// - public class ExitNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("exit"); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/SignatureHelp.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/SignatureHelp.cs deleted file mode 100644 index e24fa6358..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/SignatureHelp.cs +++ /dev/null @@ -1,50 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class SignatureHelpRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/signatureHelp"); - } - - public class SignatureHelpRegistrationOptions : TextDocumentRegistrationOptions - { - // We duplicate the properties of SignatureHelpOptions class here because - // we cannot derive from two classes. One way to get around this situation - // is to use define SignatureHelpOptions as an interface instead of a class. - public string[] TriggerCharacters { get; set; } - } - - public class ParameterInformation - { - public string Label { get; set; } - - public string Documentation { get; set; } - } - - public class SignatureInformation - { - public string Label { get; set; } - - public string Documentation { get; set; } - - public ParameterInformation[] Parameters { get; set; } - } - - public class SignatureHelp - { - public SignatureInformation[] Signatures { get; set; } - - public int? ActiveSignature { get; set; } - - public int? ActiveParameter { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/StartDebuggerEvent.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/StartDebuggerEvent.cs deleted file mode 100644 index 49adef43a..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/StartDebuggerEvent.cs +++ /dev/null @@ -1,16 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class StartDebuggerEvent - { - public static readonly - NotificationType Type = - NotificationType.Create("powerShell/startDebugger"); - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs deleted file mode 100644 index edd66f41a..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocument.cs +++ /dev/null @@ -1,313 +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.Diagnostics; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - - /// - /// An item to transfer a text document from the client to the server - /// - [DebuggerDisplay("TextDocumentItem = {Uri}:{LanguageId}:{Version}:{Text}")] - public class TextDocumentItem - { - /// - /// Gets or sets the URI which identifies the path of the - /// text document. - /// - public string Uri { get; set; } - - /// - /// The text document's language identifier. - /// - /// - public string LanguageId { get; set; } - - /// - /// The version number of this document, which will strictly increase after each change, including - /// undo/redo. - /// - /// - public int Version { get; set; } - - /// - /// The content of the opened text document. - /// - /// - public string Text { get; set; } - } - - /// - /// Defines a base parameter class for identifying a text document. - /// - [DebuggerDisplay("TextDocumentIdentifier = {Uri}")] - public class TextDocumentIdentifier - { - /// - /// Gets or sets the URI which identifies the path of the - /// text document. - /// - public string Uri { get; set; } - } - - /// - /// An identifier to denote a specific version of a text document. - /// - public class VersionedTextDocumentIdentifier : TextDocumentIdentifier - { - /// - /// The version number of this document. - /// - public int Version { get; set; } - } - - /// - /// A parameter literal used in requests to pass a text document and a position inside that document. - /// - public class TextDocumentPositionParams - { - /// - /// The text document. - /// - /// - public TextDocumentIdentifier TextDocument { get; set; } - - /// - /// The position inside the text document. - /// - /// - public Position Position { get; set; } - } - - public class DidOpenTextDocumentNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("textDocument/didOpen"); - } - - /// - /// The parameters sent in an open text document notification - /// - public class DidOpenTextDocumentParams - { - /// - /// The document that was opened. - /// - public TextDocumentItem TextDocument { get; set; } - } - - /// - /// General text document registration options. - /// - public class TextDocumentRegistrationOptions { - /// - /// A document selector to identify the scope of the registration. If set to null the document - /// selector provided on the client side will be used. - /// - public DocumentFilter[] DocumentSelector { get; set; } - } - - /// - /// A document filter denotes a document by different properties like the language, the scheme - /// of its resource, or a glob-pattern that is applied to the path. - /// - public class DocumentFilter - { - /// - /// A language id, like `powershell` - /// - public string Language { get; set; } - - /// - /// A Uri, like `file` or `untitled` - /// - public string Scheme { get; set; } - - /// - /// A glob pattern, like `*.{ps1,psd1}` - /// - public string Pattern { get; set; } - } - - public class DidCloseTextDocumentNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("textDocument/didClose"); - } - - /// - /// The parameters sent in a close text document notification. - /// - public class DidCloseTextDocumentParams - { - /// - /// The document that was closed. - /// - public TextDocumentIdentifier TextDocument { get; set; } - } - - public class DidSaveTextDocumentNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("textDocument/didSave"); - } - - /// - /// Save options. - /// - public class SaveOptions { - /// - /// The client is supposed to include the content on save. - /// - public bool? IncludeText { get; set; } - } - - public class TextDocumentSaveRegistrationOptions : TextDocumentRegistrationOptions - { - // We cannot inherit from two base classes (SaveOptions and TextDocumentRegistrationOptions) - // simultaneously, hence we repeat this IncludeText flag here. - /// - /// The client is supposed to include the content on save. - /// - public bool? IncludeText { get; set; } - } - - /// - /// The parameters sent in a save text document notification. - /// - public class DidSaveTextDocumentParams - { - /// - /// The document that was saved. - /// - public VersionedTextDocumentIdentifier TextDocument { get; set; } - - /// - /// Optional content when saved. Depends on the includeText value when the save notification was - /// included. - /// - public string Text { get; set; } - } - - public class DidChangeTextDocumentNotification - { - public static readonly - NotificationType Type = - NotificationType.Create("textDocument/didChange"); - } - - /// - /// Describe options to be used when registered for text document change events. - /// - public class TextDocumentChangeRegistrationOptions : TextDocumentRegistrationOptions - { - /// - /// How documents are synced to the server. - /// - public TextDocumentSyncKind SyncKind { get; set; } - } - - /// - /// The change text document notification's paramters. - /// - public class DidChangeTextDocumentParams - { - /// - /// The document that did change. The version number points to the version after - /// all provided content changes have been applied. - /// - public VersionedTextDocumentIdentifier TextDocument; - - /// - /// Gets or sets the list of changes to the document content. - /// - public TextDocumentChangeEvent[] ContentChanges { get; set; } - } - - public class TextDocumentChangeEvent - { - /// - /// Gets or sets the Range where the document was changed. Will - /// be null if the server's TextDocumentSyncKind is Full. - /// - public Range Range { get; set; } - - /// - /// Gets or sets the length of the Range being replaced in the - /// document. Will be null if the server's TextDocumentSyncKind is - /// Full. - /// - public int? RangeLength { get; set; } - - /// - /// Gets or sets the new text of the document. - /// - public string Text { get; set; } - } - - [DebuggerDisplay("Position = {Line}:{Character}")] - public class Position - { - /// - /// Gets or sets the zero-based line number. - /// - public int Line { get; set; } - - /// - /// Gets or sets the zero-based column number. - /// - public int Character { get; set; } - } - - [DebuggerDisplay("Start = {Start.Line}:{Start.Character}, End = {End.Line}:{End.Character}")] - public class Range - { - /// - /// Gets or sets the starting position of the range. - /// - public Position Start { get; set; } - - /// - /// Gets or sets the ending position of the range. - /// - public Position End { get; set; } - } - - [DebuggerDisplay("Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}, Uri = {Uri}")] - public class Location - { - /// - /// Gets or sets the URI indicating the file in which the location refers. - /// - public string Uri { get; set; } - - /// - /// Gets or sets the Range indicating the range in which location refers. - /// - public Range Range { get; set; } - } - - public enum FileChangeType - { - Created = 1, - - Changed, - - Deleted - } - - public class FileEvent - { - public string Uri { get; set; } - - public FileChangeType Type { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocumentClientCapabilities.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocumentClientCapabilities.cs deleted file mode 100644 index c620ad056..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/TextDocumentClientCapabilities.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class TextDocumentClientCapabilities - { - /// - /// Synchronization capabilities the client supports. - /// - public SynchronizationCapabilities Synchronization { get; set; } - - /// - /// Capabilities specific to `textDocument/completion`. - /// - public CompletionCapabilities Completion { get; set; } - - /// - /// Capabilities specific to the `textDocument/hover`. - /// - public DynamicRegistrationCapability Hover { get; set; } - - /// - /// Capabilities specific to the `textDocument/signatureHelp`. - /// - public DynamicRegistrationCapability SignatureHelp { get; set; } - - /// - /// Capabilities specific to the `textDocument/references`. - /// - public DynamicRegistrationCapability References { get; set; } - - /// - /// Capabilities specific to the `textDocument/documentHighlight`. - /// - public DynamicRegistrationCapability DocumentHighlight { get; set; } - - /// - /// Capabilities specific to the `textDocument/documentSymbol`. - /// - public DynamicRegistrationCapability DocumentSymbol { get; set; } - - /// - /// Capabilities specific to the `textDocument/formatting`. - /// - public DynamicRegistrationCapability Formatting { get; set; } - - /// - /// Capabilities specific to the `textDocument/rangeFormatting`. - /// - public DynamicRegistrationCapability RangeFormatting { get; set; } - - /// - /// Capabilities specific to the `textDocument/onTypeFormatting`. - /// - public DynamicRegistrationCapability OnTypeFormatting { get; set; } - - /// - /// Capabilities specific to the `textDocument/definition`. - /// - public DynamicRegistrationCapability Definition { get; set; } - - /// - /// Capabilities specific to the `textDocument/codeAction`. - /// - public DynamicRegistrationCapability CodeAction { get; set; } - - /// - /// Capabilities specific to the `textDocument/codeLens`. - /// - public DynamicRegistrationCapability CodeLens { get; set; } - - /// - /// Capabilities specific to the `textDocument/documentLink`. - /// - public DynamicRegistrationCapability DocumentLink { get; set; } - - /// - /// Capabilities specific to the `textDocument/rename`. - /// - public DynamicRegistrationCapability Rename { get; set; } - } - - /// - /// Class to represent capabilities specific to `textDocument/completion`. - /// - public class CompletionCapabilities : DynamicRegistrationCapability - { - /// - /// The client supports the following `CompletionItem` specific capabilities. - /// - /// - public CompletionItemCapabilities CompletionItem { get; set; } - } - - /// - /// Class to represent capabilities specific to `CompletionItem`. - /// - public class CompletionItemCapabilities - { - /// - /// Client supports snippets as insert text. - /// - /// A snippet can define tab stops and placeholders with `$1`, `$2` - /// and `${3:foo}`. `$0` defines the final tab stop, it defaults to - /// the end of the snippet. Placeholders with equal identifiers are linked, - /// that is typing in one will update others too. - /// - public bool? SnippetSupport { get; set; } - } - - /// - /// Class to represent synchronization capabilities the client supports. - /// - public class SynchronizationCapabilities : DynamicRegistrationCapability - { - /// - /// The client supports sending will save notifications. - /// - public bool? WillSave { get; set; } - - /// - /// The client supports sending a will save request and waits for a response - /// providing text edits which will be applied to the document before it is save. - /// - public bool? WillSaveWaitUntil { get; set; } - - /// - /// The client supports did save notifications. - /// - public bool? DidSave { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceClientCapabilities.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceClientCapabilities.cs deleted file mode 100644 index e83fdec5a..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceClientCapabilities.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public class WorkspaceClientCapabilities - { - /// - /// The client supports applying batch edits to the workspace by - /// by supporting the request `workspace/applyEdit' - /// /// - public bool? ApplyEdit { get; set; } - - - /// - /// Capabilities specific to `WorkspaceEdit`. - /// - public WorkspaceEditCapabilities WorkspaceEdit { get; set; } - - /// - /// Capabilities specific to the `workspace/didChangeConfiguration` notification. - /// - public DynamicRegistrationCapability DidChangeConfiguration { get; set; } - - /// - /// Capabilities specific to the `workspace/didChangeWatchedFiles` notification. - /// - public DynamicRegistrationCapability DidChangeWatchedFiles { get; set; } - - /// - /// Capabilities specific to the `workspace/symbol` request. - /// - public DynamicRegistrationCapability Symbol { get; set; } - - /// - /// Capabilities specific to the `workspace/executeCommand` request. - /// - public DynamicRegistrationCapability ExecuteCommand { get; set; } - } - - /// - /// Class to represent capabilities specific to `WorkspaceEdit`. - /// - public class WorkspaceEditCapabilities - { - /// - /// The client supports versioned document changes in `WorkspaceEdit` - /// - public bool? DocumentChanges { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceSymbols.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceSymbols.cs deleted file mode 100644 index 97e990345..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceSymbols.cs +++ /dev/null @@ -1,73 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer -{ - public enum SymbolKind - { - File = 1, - Module = 2, - Namespace = 3, - Package = 4, - Class = 5, - Method = 6, - Property = 7, - Field = 8, - Constructor = 9, - Enum = 10, - Interface = 11, - Function = 12, - Variable = 13, - Constant = 14, - String = 15, - Number = 16, - Boolean = 17, - Array = 18, - } - - public class SymbolInformation - { - public string Name { get; set; } - - public SymbolKind Kind { get; set; } - - public Location Location { get; set; } - - public string ContainerName { get; set;} - } - - public class DocumentSymbolRequest - { - public static readonly - RequestType Type = - RequestType.Create("textDocument/documentSymbol"); - } - - /// - /// Parameters for a DocumentSymbolRequest - /// - public class DocumentSymbolParams - { - /// - /// The text document. - /// - public TextDocumentIdentifier TextDocument { get; set; } - } - - public class WorkspaceSymbolRequest - { - public static readonly - RequestType Type = - RequestType.Create("workspace/symbol"); - } - - public class WorkspaceSymbolParams - { - public string Query { get; set;} - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/AbstractMessageType.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/AbstractMessageType.cs deleted file mode 100644 index 1b2fc37f9..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/AbstractMessageType.cs +++ /dev/null @@ -1,35 +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.Protocol.MessageProtocol -{ - /// - /// Defines an event type with a particular method name. - /// - /// The parameter type for this event. - public class AbstractMessageType - { - private string _method; - private int _numberOfParams; - - /// - /// Gets the method name for the event type. - /// - public string Method { get { return _method; } } - - /// - /// Gets the number of parameters. - /// - public int NumberOfParams { get; } - - public AbstractMessageType(string method, int numberOfParams) - { - _method = method; - _numberOfParams = numberOfParams; - } - } -} - - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ChannelBase.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ChannelBase.cs deleted file mode 100644 index ea4922938..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ChannelBase.cs +++ /dev/null @@ -1,68 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - /// - /// Defines a base implementation for servers and their clients over a - /// single kind of communication channel. - /// - public abstract class ChannelBase - { - /// - /// Gets the MessageReader for reading messages from the channel. - /// - public MessageReader MessageReader { get; protected set; } - - /// - /// Gets the MessageWriter for writing messages to the channel. - /// - public MessageWriter MessageWriter { get; protected set; } - - /// - /// Starts the channel and initializes the MessageDispatcher. - /// - /// The type of message protocol used by the channel. - public void Start(MessageProtocolType messageProtocolType) - { - IMessageSerializer messageSerializer = null; - if (messageProtocolType == MessageProtocolType.LanguageServer) - { - messageSerializer = new JsonRpcMessageSerializer(); - } - else - { - messageSerializer = new V8MessageSerializer(); - } - - this.Initialize(messageSerializer); - } - - /// - /// Stops the channel. - /// - public void Stop() - { - this.Shutdown(); - } - - /// - /// A method to be implemented by subclasses to handle the - /// actual initialization of the channel and the creation and - /// assignment of the MessageReader and MessageWriter properties. - /// - /// The IMessageSerializer to use for message serialization. - protected abstract void Initialize(IMessageSerializer messageSerializer); - - /// - /// A method to be implemented by subclasses to handle shutdown - /// of the channel once Stop is called. - /// - protected abstract void Shutdown(); - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs deleted file mode 100644 index 1d3bd8a7d..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.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.IO.Pipes; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - public class NamedPipeClientChannel : ChannelBase - { - private ILogger logger; - private NamedPipeClientStream pipeClient; - - // This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard. - private const int CurrentUserOnly = 536870912; - - private const string NAMED_PIPE_UNIX_PREFIX = "CoreFxPipe_"; - - public NamedPipeClientChannel( - NamedPipeClientStream pipeClient, - ILogger logger) - { - this.pipeClient = pipeClient; - this.logger = logger; - } - - protected override void Initialize(IMessageSerializer messageSerializer) - { - this.MessageReader = - new MessageReader( - this.pipeClient, - messageSerializer, - this.logger); - - this.MessageWriter = - new MessageWriter( - this.pipeClient, - messageSerializer, - this.logger); - } - - protected override void Shutdown() - { - if (this.pipeClient != null) - { - this.pipeClient.Dispose(); - } - } - - public static async Task ConnectAsync( - string pipeFile, - MessageProtocolType messageProtocolType, - ILogger logger) - { - string pipeName = System.IO.Path.GetFileName(pipeFile); - - var options = PipeOptions.Asynchronous; - // on macOS and Linux, the named pipe name is prefixed by .NET Core - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - pipeName = pipeFile.Split(new [] {NAMED_PIPE_UNIX_PREFIX}, StringSplitOptions.None)[1]; - options |= (PipeOptions)CurrentUserOnly; - } - - var pipeClient = - new NamedPipeClientStream( - ".", - pipeName, - PipeDirection.InOut, - options); - - await pipeClient.ConnectAsync(); - var clientChannel = new NamedPipeClientChannel(pipeClient, logger); - clientChannel.Start(messageProtocolType); - - return clientChannel; - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs deleted file mode 100644 index 870640c49..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs +++ /dev/null @@ -1,57 +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 Microsoft.PowerShell.EditorServices.Utility; -using System.IO.Pipes; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - public class NamedPipeServerChannel : ChannelBase - { - private ILogger logger; - private NamedPipeServerStream inOutPipeServer; - private NamedPipeServerStream outPipeServer; - - public NamedPipeServerChannel( - NamedPipeServerStream inOutPipeServer, - ILogger logger) - { - this.inOutPipeServer = inOutPipeServer; - this.logger = logger; - } - public NamedPipeServerChannel( - NamedPipeServerStream inOutPipeServer, - NamedPipeServerStream outPipeServer, - ILogger logger) - { - this.inOutPipeServer = inOutPipeServer; - this.outPipeServer = outPipeServer; - this.logger = logger; - } - - protected override void Initialize(IMessageSerializer messageSerializer) - { - this.MessageReader = - new MessageReader( - this.inOutPipeServer, - messageSerializer, - this.logger); - - this.MessageWriter = - new MessageWriter( - this.outPipeServer ?? this.inOutPipeServer, - messageSerializer, - this.logger); - } - - protected override void Shutdown() - { - // The server listener will take care of the pipe server - this.inOutPipeServer = null; - this.outPipeServer = null; - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs deleted file mode 100644 index df79c806a..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs +++ /dev/null @@ -1,205 +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 Microsoft.PowerShell.EditorServices.Utility; -using Microsoft.Win32.SafeHandles; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Pipes; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Security.AccessControl; -using System.Security.Principal; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - public class NamedPipeServerListener : ServerListenerBase - { - // This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard. - private const int CurrentUserOnly = 0x20000000; - - // In .NET Framework, NamedPipeServerStream has a constructor that takes in a PipeSecurity object. We will use reflection to call the constructor, - // since .NET Framework doesn't have the `CurrentUserOnly` PipeOption. - // doc: https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeserverstream.-ctor?view=netframework-4.7.2#System_IO_Pipes_NamedPipeServerStream__ctor_System_String_System_IO_Pipes_PipeDirection_System_Int32_System_IO_Pipes_PipeTransmissionMode_System_IO_Pipes_PipeOptions_System_Int32_System_Int32_System_IO_Pipes_PipeSecurity_ - private static readonly ConstructorInfo s_netFrameworkPipeServerConstructor = - typeof(NamedPipeServerStream).GetConstructor(new [] { typeof(string), typeof(PipeDirection), typeof(int), typeof(PipeTransmissionMode), typeof(PipeOptions), typeof(int), typeof(int), typeof(PipeSecurity) }); - - private readonly ILogger _logger; - private readonly string _inOutPipeName; - private readonly string _outPipeName; - - private NamedPipeServerStream _inOutPipeServer; - private NamedPipeServerStream _outPipeServer; - - public NamedPipeServerListener( - MessageProtocolType messageProtocolType, - string inOutPipeName, - ILogger logger) - : base(messageProtocolType) - { - _logger = logger; - _inOutPipeName = inOutPipeName; - } - - public NamedPipeServerListener( - MessageProtocolType messageProtocolType, - string inPipeName, - string outPipeName, - ILogger logger) - : base(messageProtocolType) - { - _logger = logger; - _inOutPipeName = inPipeName; - _outPipeName = outPipeName; - } - - public override void Start() - { - try - { - _inOutPipeServer = ConnectNamedPipe(_inOutPipeName, _outPipeName, out _outPipeServer); - ListenForConnection(); - } - catch (IOException e) - { - _logger.Write( - LogLevel.Verbose, - "Named pipe server failed to start due to exception:\r\n\r\n" + e.Message); - - throw e; - } - } - - public override void Stop() - { - if (_inOutPipeServer != null) - { - _logger.Write(LogLevel.Verbose, "Named pipe server shutting down..."); - - _inOutPipeServer.Dispose(); - - _logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); - } - - if (_outPipeServer != null) - { - _logger.Write(LogLevel.Verbose, $"Named out pipe server {_outPipeServer} shutting down..."); - - _outPipeServer.Dispose(); - - _logger.Write(LogLevel.Verbose, $"Named out pipe server {_outPipeServer} has been disposed."); - } - } - - private static NamedPipeServerStream ConnectNamedPipe( - string inOutPipeName, - string outPipeName, - out NamedPipeServerStream outPipe) - { - // .NET Core implementation is simplest so try that first - if (Utils.IsNetCore) - { - outPipe = outPipeName == null - ? null - : new NamedPipeServerStream( - pipeName: outPipeName, - direction: PipeDirection.Out, - maxNumberOfServerInstances: 1, - transmissionMode: PipeTransmissionMode.Byte, - options: (PipeOptions)CurrentUserOnly); - - return new NamedPipeServerStream( - pipeName: inOutPipeName, - direction: PipeDirection.InOut, - maxNumberOfServerInstances: 1, - transmissionMode: PipeTransmissionMode.Byte, - options: PipeOptions.Asynchronous | (PipeOptions)CurrentUserOnly); - } - - // Now deal with Windows PowerShell - // We need to use reflection to get a nice constructor - - PipeSecurity pipeSecurity = new PipeSecurity(); - - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - - if (principal.IsInRole(WindowsBuiltInRole.Administrator)) - { - // Allow the Administrators group full access to the pipe. - pipeSecurity.AddAccessRule(new PipeAccessRule( - new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)), - PipeAccessRights.FullControl, AccessControlType.Allow)); - } - else - { - // Allow the current user read/write access to the pipe. - pipeSecurity.AddAccessRule(new PipeAccessRule( - WindowsIdentity.GetCurrent().User, - PipeAccessRights.ReadWrite, AccessControlType.Allow)); - } - - outPipe = outPipeName == null - ? null - : (NamedPipeServerStream)s_netFrameworkPipeServerConstructor.Invoke( - new object[] { - outPipeName, - PipeDirection.InOut, - 1, // maxNumberOfServerInstances - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, - 1024, // inBufferSize - 1024, // outBufferSize - pipeSecurity - }); - - return (NamedPipeServerStream)s_netFrameworkPipeServerConstructor.Invoke( - new object[] { - inOutPipeName, - PipeDirection.InOut, - 1, // maxNumberOfServerInstances - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, - 1024, // inBufferSize - 1024, // outBufferSize - pipeSecurity - }); - } - - private void ListenForConnection() - { - Task.Factory.StartNew(async () => - { - try - { - var connectionTasks = new List {WaitForConnectionAsync(_inOutPipeServer)}; - if (_outPipeServer != null) - { - connectionTasks.Add(WaitForConnectionAsync(_outPipeServer)); - } - - await Task.WhenAll(connectionTasks); - OnClientConnect(new NamedPipeServerChannel(_inOutPipeServer, _outPipeServer, _logger)); - } - catch (Exception e) - { - _logger.WriteException( - "An unhandled exception occurred while listening for a named pipe client connection", - e); - - throw; - } - }); - } - - private static async Task WaitForConnectionAsync(NamedPipeServerStream pipeServerStream) - { - await pipeServerStream.WaitForConnectionAsync(); - await pipeServerStream.FlushAsync(); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs deleted file mode 100644 index 433f6aabb..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs +++ /dev/null @@ -1,42 +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.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - public abstract class ServerListenerBase : IServerListener - where TChannel : ChannelBase - { - private MessageProtocolType messageProtocolType; - - public ServerListenerBase(MessageProtocolType messageProtocolType) - { - this.messageProtocolType = messageProtocolType; - } - - public abstract void Start(); - - public abstract void Stop(); - - public event EventHandler ClientConnect; - - protected void OnClientConnect(TChannel channel) - { - channel.Start(this.messageProtocolType); - this.ClientConnect?.Invoke(this, channel); - } - } - - public interface IServerListener - { - void Start(); - - void Stop(); - - event EventHandler ClientConnect; - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs deleted file mode 100644 index 86fe7ce7a..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs +++ /dev/null @@ -1,123 +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.Diagnostics; -using System.IO; -using System.Text; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - /// - /// Provides a client implementation for the standard I/O channel. - /// Launches the server process and then attaches to its console - /// streams. - /// - public class StdioClientChannel : ChannelBase - { - private string serviceProcessPath; - private string serviceProcessArguments; - - private ILogger logger; - private Stream inputStream; - private Stream outputStream; - private Process serviceProcess; - - /// - /// Gets the process ID of the server process. - /// - public int ProcessId { get; private set; } - - /// - /// Initializes an instance of the StdioClient. - /// - /// The full path to the server process executable. - /// Optional arguments to pass to the service process executable. - public StdioClientChannel( - string serverProcessPath, - ILogger logger, - params string[] serverProcessArguments) - { - this.logger = logger; - this.serviceProcessPath = serverProcessPath; - - if (serverProcessArguments != null) - { - this.serviceProcessArguments = - string.Join( - " ", - serverProcessArguments); - } - } - - protected override void Initialize(IMessageSerializer messageSerializer) - { - this.serviceProcess = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = this.serviceProcessPath, - Arguments = this.serviceProcessArguments, - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - StandardOutputEncoding = Encoding.UTF8, - }, - EnableRaisingEvents = true, - }; - - // Start the process - this.serviceProcess.Start(); - - this.ProcessId = this.serviceProcess.Id; - - // Open the standard input/output streams - this.inputStream = this.serviceProcess.StandardOutput.BaseStream; - this.outputStream = this.serviceProcess.StandardInput.BaseStream; - - // Set up the message reader and writer - this.MessageReader = - new MessageReader( - this.inputStream, - messageSerializer, - this.logger); - - this.MessageWriter = - new MessageWriter( - this.outputStream, - messageSerializer, - this.logger); - } - - protected override void Shutdown() - { - if (this.inputStream != null) - { - this.inputStream.Dispose(); - this.inputStream = null; - } - - if (this.outputStream != null) - { - this.outputStream.Dispose(); - this.outputStream = null; - } - - if (this.MessageReader != null) - { - this.MessageReader = null; - } - - if (this.MessageWriter != null) - { - this.MessageWriter = null; - } - - this.serviceProcess.Kill(); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs deleted file mode 100644 index 95318d860..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs +++ /dev/null @@ -1,64 +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.IO; -using System.Text; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - /// - /// Provides a server implementation for the standard I/O channel. - /// When started in a process, attaches to the console I/O streams - /// to communicate with the client that launched the process. - /// - public class StdioServerChannel : ChannelBase - { - private ILogger logger; - private Stream inputStream; - private Stream outputStream; - - public StdioServerChannel(ILogger logger) - { - this.logger = logger; - } - - protected override void Initialize(IMessageSerializer messageSerializer) - { - if (System.Console.InputEncoding != Encoding.UTF8) - { - System.Console.InputEncoding = Encoding.UTF8; - } - - if (System.Console.OutputEncoding != Encoding.UTF8) - { - System.Console.OutputEncoding = Encoding.UTF8; - } - - // Open the standard input/output streams - this.inputStream = System.Console.OpenStandardInput(); - this.outputStream = System.Console.OpenStandardOutput(); - - // Set up the reader and writer - this.MessageReader = - new MessageReader( - this.inputStream, - messageSerializer, - this.logger); - - this.MessageWriter = - new MessageWriter( - this.outputStream, - messageSerializer, - this.logger); - } - - protected override void Shutdown() - { - // No default implementation needed, streams will be - // disposed on process shutdown. - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs deleted file mode 100644 index 6b850035d..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs +++ /dev/null @@ -1,36 +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.IO; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel -{ - public class StdioServerListener : ServerListenerBase - { - private ILogger logger; - - public StdioServerListener( - MessageProtocolType messageProtocolType, - ILogger logger) - : base(messageProtocolType) - { - this.logger = logger; - } - - public override void Start() - { - // Client is connected immediately because stdio - // will buffer all I/O until we get to it - this.OnClientConnect( - new StdioServerChannel( - this.logger)); - } - - public override void Stop() - { - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Constants.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Constants.cs deleted file mode 100644 index d443dc12d..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Constants.cs +++ /dev/null @@ -1,31 +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 Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public static class Constants - { - public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n"; - public static readonly JsonSerializerSettings JsonSerializerSettings; - - static Constants() - { - JsonSerializerSettings = new JsonSerializerSettings(); - - // Camel case all object properties - JsonSerializerSettings.ContractResolver = - new CamelCasePropertyNamesContractResolver(); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/EventContext.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/EventContext.cs deleted file mode 100644 index 4da757913..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/EventContext.cs +++ /dev/null @@ -1,34 +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 Newtonsoft.Json.Linq; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - /// - /// Provides context for a received event so that handlers - /// can write events back to the channel. - /// - public class EventContext - { - private MessageWriter messageWriter; - - public EventContext(MessageWriter messageWriter) - { - this.messageWriter = messageWriter; - } - - public async Task SendEventAsync( - NotificationType eventType, - TParams eventParams) - { - await this.messageWriter.WriteEventAsync( - eventType, - eventParams); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/EventType.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/EventType.cs deleted file mode 100644 index 58e2f1f59..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/EventType.cs +++ /dev/null @@ -1,30 +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.Protocol.MessageProtocol -{ - /// - /// Defines an event type with a particular method name. - /// - /// The parameter type for this event. - public class NotificationType : AbstractMessageType - { - private NotificationType(string method) : base(method, 1) - { - - } - - /// - /// Creates an EventType instance with the given parameter type and method name. - /// - /// The method name of the event. - /// A new EventType instance for the defined type. - public static NotificationType Create(string method) - { - return new NotificationType(method); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs deleted file mode 100644 index 04e1ba9d6..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs +++ /dev/null @@ -1,25 +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.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public interface IMessageHandlers - { - void SetRequestHandler( - RequestType requestType, - Func, Task> requestHandler); - - void SetRequestHandler( - RequestType0 requestType0, - Func, Task> requestHandler); - - void SetEventHandler( - NotificationType eventType, - Func eventHandler); - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs deleted file mode 100644 index 804bbe74c..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs +++ /dev/null @@ -1,25 +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.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public interface IMessageSender - { - Task SendEventAsync( - NotificationType eventType, - TParams eventParams); - - Task SendRequestAsync( - RequestType requestType, - TParams requestParams, - bool waitForResponse); - - Task SendRequestAsync( - RequestType0 requestType0); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSerializer.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSerializer.cs deleted file mode 100644 index eafd8f209..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSerializer.cs +++ /dev/null @@ -1,30 +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 Newtonsoft.Json.Linq; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - /// - /// Defines a common interface for message serializers. - /// - public interface IMessageSerializer - { - /// - /// Serializes a Message to a JObject. - /// - /// The message to be serialized. - /// A JObject which contains the JSON representation of the message. - JObject SerializeMessage(Message message); - - /// - /// Deserializes a JObject to a Messsage. - /// - /// The JObject containing the JSON representation of the message. - /// The Message that was represented by the JObject. - Message DeserializeMessage(JObject messageJson); - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Message.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Message.cs deleted file mode 100644 index 0d304cc03..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Message.cs +++ /dev/null @@ -1,136 +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.Diagnostics; -using Newtonsoft.Json.Linq; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - /// - /// Defines all possible message types. - /// - public enum MessageType - { - Unknown, - Request, - Response, - Event - } - - /// - /// Provides common details for protocol messages of any format. - /// - [DebuggerDisplay("MessageType = {MessageType.ToString()}, Method = {Method}, Id = {Id}")] - public class Message - { - /// - /// Gets or sets the message type. - /// - public MessageType MessageType { get; set; } - - /// - /// Gets or sets the message's sequence ID. - /// - public string Id { get; set; } - - /// - /// Gets or sets the message's method/command name. - /// - public string Method { get; set; } - - /// - /// Gets or sets a JToken containing the contents of the message. - /// - public JToken Contents { get; set; } - - /// - /// Gets or sets a JToken containing error details. - /// - public JToken Error { get; set; } - - /// - /// Creates a message with an Unknown type. - /// - /// A message with Unknown type. - public static Message Unknown() - { - return new Message - { - MessageType = MessageType.Unknown - }; - } - - /// - /// Creates a message with a Request type. - /// - /// The sequence ID of the request. - /// The method name of the request. - /// The contents of the request. - /// A message with a Request type. - public static Message Request(string id, string method, JToken contents) - { - return new Message - { - MessageType = MessageType.Request, - Id = id, - Method = method, - Contents = contents - }; - } - - /// - /// Creates a message with a Response type. - /// - /// The sequence ID of the original request. - /// The method name of the original request. - /// The contents of the response. - /// A message with a Response type. - public static Message Response(string id, string method, JToken contents) - { - return new Message - { - MessageType = MessageType.Response, - Id = id, - Method = method, - Contents = contents - }; - } - - /// - /// Creates a message with a Response type and error details. - /// - /// The sequence ID of the original request. - /// The method name of the original request. - /// The error details of the response. - /// A message with a Response type and error details. - public static Message ResponseError(string id, string method, JToken error) - { - return new Message - { - MessageType = MessageType.Response, - Id = id, - Method = method, - Error = error - }; - } - - /// - /// Creates a message with an Event type. - /// - /// The method name of the event. - /// The contents of the event. - /// A message with an Event type. - public static Message Event(string method, JToken contents) - { - return new Message - { - MessageType = MessageType.Event, - Method = method, - Contents = contents - }; - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs deleted file mode 100644 index ff7ae487e..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs +++ /dev/null @@ -1,181 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public class MessageDispatcher : IMessageHandlers, IMessageDispatcher - { - #region Fields - - private ILogger logger; - - private Dictionary> requestHandlers = - new Dictionary>(); - - private Dictionary> eventHandlers = - new Dictionary>(); - - #endregion - - #region Constructors - - public MessageDispatcher(ILogger logger) - { - this.logger = logger; - } - - #endregion - - #region Public Methods - - public void SetRequestHandler( - RequestType requestType, - Func, Task> requestHandler) - { - bool overrideExisting = true; - - if (overrideExisting) - { - // Remove the existing handler so a new one can be set - this.requestHandlers.Remove(requestType.Method); - } - - this.requestHandlers.Add( - requestType.Method, - (requestMessage, messageWriter) => - { - var requestContext = - new RequestContext( - requestMessage, - messageWriter); - - TParams typedParams = default(TParams); - if (requestMessage.Contents != null) - { - // TODO: Catch parse errors! - typedParams = requestMessage.Contents.ToObject(); - } - - return requestHandler(typedParams, requestContext); - }); - } - - public void SetRequestHandler( - RequestType0 requestType0, - Func, Task> requestHandler) - { - this.SetRequestHandler( - RequestType.ConvertToRequestType(requestType0), - (param1, requestContext) => - { - return requestHandler(requestContext); - }); - } - - public void SetEventHandler( - NotificationType eventType, - Func eventHandler) - { - bool overrideExisting = true; - - if (overrideExisting) - { - // Remove the existing handler so a new one can be set - this.eventHandlers.Remove(eventType.Method); - } - - this.eventHandlers.Add( - eventType.Method, - (eventMessage, messageWriter) => - { - var eventContext = new EventContext(messageWriter); - - TParams typedParams = default(TParams); - if (eventMessage.Contents != null) - { - // TODO: Catch parse errors! - typedParams = eventMessage.Contents.ToObject(); - } - - return eventHandler(typedParams, eventContext); - }); - } - - #endregion - - #region Private Methods - - public async Task DispatchMessageAsync( - Message messageToDispatch, - MessageWriter messageWriter) - { - Task handlerToAwait = null; - - if (messageToDispatch.MessageType == MessageType.Request) - { - Func requestHandler = null; - if (this.requestHandlers.TryGetValue(messageToDispatch.Method, out requestHandler)) - { - handlerToAwait = requestHandler(messageToDispatch, messageWriter); - } - else - { - // TODO: Message not supported error - this.logger.Write(LogLevel.Warning, $"MessageDispatcher: No handler registered for Request type '{messageToDispatch.Method}'"); - } - } - else if (messageToDispatch.MessageType == MessageType.Event) - { - Func eventHandler = null; - if (this.eventHandlers.TryGetValue(messageToDispatch.Method, out eventHandler)) - { - handlerToAwait = eventHandler(messageToDispatch, messageWriter); - } - else - { - // TODO: Message not supported error - this.logger.Write(LogLevel.Warning, $"MessageDispatcher: No handler registered for Event type '{messageToDispatch.Method}'"); - } - } - else - { - // TODO: Return message not supported - this.logger.Write(LogLevel.Warning, $"MessageDispatcher received unknown message type of method '{messageToDispatch.Method}'"); - } - - if (handlerToAwait != null) - { - try - { - await handlerToAwait; - } - catch (TaskCanceledException) - { - // Some tasks may be cancelled due to legitimate - // timeouts so don't let those exceptions go higher. - } - catch (AggregateException e) - { - if (!(e.InnerExceptions[0] is TaskCanceledException)) - { - // Cancelled tasks aren't a problem, so rethrow - // anything that isn't a TaskCanceledException - throw e; - } - } - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageParseException.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageParseException.cs deleted file mode 100644 index 2155bb14a..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageParseException.cs +++ /dev/null @@ -1,23 +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.Protocol.MessageProtocol -{ - public class MessageParseException : Exception - { - public string OriginalMessageText { get; private set; } - - public MessageParseException( - string originalMessageText, - string errorMessage, - params object[] errorMessageArgs) - : base(string.Format(errorMessage, errorMessageArgs)) - { - this.OriginalMessageText = originalMessageText; - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageProtocolType.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageProtocolType.cs deleted file mode 100644 index 7aa3d6c81..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageProtocolType.cs +++ /dev/null @@ -1,23 +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.Protocol.MessageProtocol -{ - /// - /// Defines the possible message protocol types. - /// - public enum MessageProtocolType - { - /// - /// Identifies the language server message protocol. - /// - LanguageServer, - - /// - /// Identifies the debug adapter message protocol. - /// - DebugAdapter - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs deleted file mode 100644 index fec0d175b..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs +++ /dev/null @@ -1,284 +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 Microsoft.PowerShell.EditorServices.Utility; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public class MessageReader - { - #region Private Fields - - public const int DefaultBufferSize = 8192; - public const double BufferResizeTrigger = 0.25; - - private const int CR = 0x0D; - private const int LF = 0x0A; - private static string[] NewLineDelimiters = new string[] { Environment.NewLine }; - - private ILogger logger; - private Stream inputStream; - private IMessageSerializer messageSerializer; - private Encoding messageEncoding; - - private ReadState readState; - private bool needsMoreData = true; - private int readOffset; - private int bufferEndOffset; - private byte[] messageBuffer = new byte[DefaultBufferSize]; - - private int expectedContentLength; - private Dictionary messageHeaders; - - enum ReadState - { - Headers, - Content - } - - #endregion - - #region Constructors - - public MessageReader( - Stream inputStream, - IMessageSerializer messageSerializer, - ILogger logger, - Encoding messageEncoding = null) - { - Validate.IsNotNull("streamReader", inputStream); - Validate.IsNotNull("messageSerializer", messageSerializer); - - this.logger = logger; - this.inputStream = inputStream; - this.messageSerializer = messageSerializer; - - this.messageEncoding = messageEncoding; - if (messageEncoding == null) - { - this.messageEncoding = Encoding.UTF8; - } - - this.messageBuffer = new byte[DefaultBufferSize]; - } - - #endregion - - #region Public Methods - - public async Task ReadMessageAsync() - { - string messageContent = null; - - // Do we need to read more data or can we process the existing buffer? - while (!this.needsMoreData || await this.ReadNextChunkAsync()) - { - // Clear the flag since we should have what we need now - this.needsMoreData = false; - - // Do we need to look for message headers? - if (this.readState == ReadState.Headers && - !this.TryReadMessageHeaders()) - { - // If we don't have enough data to read headers yet, keep reading - this.needsMoreData = true; - continue; - } - - // Do we need to look for message content? - if (this.readState == ReadState.Content && - !this.TryReadMessageContent(out messageContent)) - { - // If we don't have enough data yet to construct the content, keep reading - this.needsMoreData = true; - continue; - } - - // We've read a message now, break out of the loop - break; - } - - // Get the JObject for the JSON content - JObject messageObject = JObject.Parse(messageContent); - - // Deserialize the message from the parsed JSON message - Message parsedMessage = this.messageSerializer.DeserializeMessage(messageObject); - - // Log message info - initial capacity for StringBuilder varies depending on whether - // the log level is Diagnostic where JsonRpc message payloads are logged and vary in size - // from 1K up to the edited file size. When not logging message payloads, the typical - // request log message size is under 256 chars. - var logStrBld = - new StringBuilder(this.logger.MinimumConfiguredLogLevel == LogLevel.Diagnostic ? 4096 : 256) - .Append("Received ") - .Append(parsedMessage.MessageType) - .Append(" '").Append(parsedMessage.Method).Append("'"); - - if (!string.IsNullOrEmpty(parsedMessage.Id)) - { - logStrBld.Append(" with id ").Append(parsedMessage.Id); - } - - if (this.logger.MinimumConfiguredLogLevel == LogLevel.Diagnostic) - { - // Log the JSON representation of the message payload at the Diagnostic log level - string jsonPayload = messageObject.ToString(Formatting.Indented); - logStrBld.Append(Environment.NewLine).Append(Environment.NewLine).Append(jsonPayload); - } - - this.logger.Write(LogLevel.Verbose, logStrBld.ToString()); - - return parsedMessage; - } - - #endregion - - #region Private Methods - - private async Task ReadNextChunkAsync() - { - // Do we need to resize the buffer? See if less than 1/4 of the space is left. - if (((double)(this.messageBuffer.Length - this.bufferEndOffset) / this.messageBuffer.Length) < 0.25) - { - // Double the size of the buffer - Array.Resize( - ref this.messageBuffer, - this.messageBuffer.Length * 2); - } - - // Read the next chunk into the message buffer - int readLength = - await this.inputStream.ReadAsync( - this.messageBuffer, - this.bufferEndOffset, - this.messageBuffer.Length - this.bufferEndOffset); - - this.bufferEndOffset += readLength; - - if (readLength == 0) - { - // If ReadAsync returns 0 then it means that the stream was - // closed unexpectedly (usually due to the client application - // ending suddenly). For now, just terminate the language - // server immediately. - // TODO: Provide a more graceful shutdown path - throw new EndOfStreamException( - "MessageReader's input stream ended unexpectedly, terminating."); - } - - return true; - } - - private bool TryReadMessageHeaders() - { - int scanOffset = this.readOffset; - - // Scan for the final double-newline that marks the - // end of the header lines - while (scanOffset + 3 < this.bufferEndOffset && - (this.messageBuffer[scanOffset] != CR || - this.messageBuffer[scanOffset + 1] != LF || - this.messageBuffer[scanOffset + 2] != CR || - this.messageBuffer[scanOffset + 3] != LF)) - { - scanOffset++; - } - - // No header or body separator found (e.g CRLFCRLF) - if (scanOffset + 3 >= this.bufferEndOffset) - { - return false; - } - - this.messageHeaders = new Dictionary(); - - var headers = - Encoding.ASCII - .GetString(this.messageBuffer, this.readOffset, scanOffset) - .Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries); - - // Read each header and store it in the dictionary - foreach (var header in headers) - { - int currentLength = header.IndexOf(':'); - if (currentLength == -1) - { - throw new ArgumentException("Message header must separate key and value using :"); - } - - var key = header.Substring(0, currentLength); - var value = header.Substring(currentLength + 1).Trim(); - this.messageHeaders[key] = value; - } - - // Make sure a Content-Length header was present, otherwise it - // is a fatal error - string contentLengthString = null; - if (!this.messageHeaders.TryGetValue("Content-Length", out contentLengthString)) - { - throw new MessageParseException("", "Fatal error: Content-Length header must be provided."); - } - - // Parse the content length to an integer - if (!int.TryParse(contentLengthString, out this.expectedContentLength)) - { - throw new MessageParseException("", "Fatal error: Content-Length value is not an integer."); - } - - // Skip past the headers plus the newline characters - this.readOffset += scanOffset + 4; - - // Done reading headers, now read content - this.readState = ReadState.Content; - - return true; - } - - private bool TryReadMessageContent(out string messageContent) - { - messageContent = null; - - // Do we have enough bytes to reach the expected length? - if ((this.bufferEndOffset - this.readOffset) < this.expectedContentLength) - { - return false; - } - - // Convert the message contents to a string using the specified encoding - messageContent = - this.messageEncoding.GetString( - this.messageBuffer, - this.readOffset, - this.expectedContentLength); - - // Move the remaining bytes to the front of the buffer for the next message - var remainingByteCount = this.bufferEndOffset - (this.expectedContentLength + this.readOffset); - Buffer.BlockCopy( - this.messageBuffer, - this.expectedContentLength + this.readOffset, - this.messageBuffer, - 0, - remainingByteCount); - - // Reset the offsets for the next read - this.readOffset = 0; - this.bufferEndOffset = remainingByteCount; - - // Done reading content, now look for headers - this.readState = ReadState.Headers; - - return true; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs deleted file mode 100644 index 5baa40038..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs +++ /dev/null @@ -1,163 +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 Microsoft.PowerShell.EditorServices.Utility; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using System; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public class MessageWriter - { - #region Private Fields - - private ILogger logger; - private Stream outputStream; - private IMessageSerializer messageSerializer; - private AsyncLock writeLock = new AsyncLock(); - - private JsonSerializer contentSerializer = - JsonSerializer.Create( - Constants.JsonSerializerSettings); - - #endregion - - #region Constructors - - public MessageWriter( - Stream outputStream, - IMessageSerializer messageSerializer, - ILogger logger) - { - Validate.IsNotNull("streamWriter", outputStream); - Validate.IsNotNull("messageSerializer", messageSerializer); - - this.logger = logger; - this.outputStream = outputStream; - this.messageSerializer = messageSerializer; - } - - #endregion - - #region Public Methods - - // TODO: This method should be made protected or private - - public async Task WriteMessageAsync(Message messageToWrite) - { - Validate.IsNotNull("messageToWrite", messageToWrite); - - // Serialize the message - JObject messageObject = - this.messageSerializer.SerializeMessage( - messageToWrite); - - // Log message info - initial capacity for StringBuilder varies depending on whether - // the log level is Diagnostic where JsonRpc message payloads are logged and vary - // in size from 1K up to 225K chars. When not logging message payloads, the typical - // response log message size is under 256 chars. - var logStrBld = - new StringBuilder(this.logger.MinimumConfiguredLogLevel == LogLevel.Diagnostic ? 4096 : 256) - .Append("Writing ") - .Append(messageToWrite.MessageType) - .Append(" '").Append(messageToWrite.Method).Append("'"); - - if (!string.IsNullOrEmpty(messageToWrite.Id)) - { - logStrBld.Append(" with id ").Append(messageToWrite.Id); - } - - if (this.logger.MinimumConfiguredLogLevel == LogLevel.Diagnostic) - { - // Log the JSON representation of the message payload at the Diagnostic log level - string jsonPayload = - JsonConvert.SerializeObject( - messageObject, - Formatting.Indented, - Constants.JsonSerializerSettings); - - logStrBld.Append(Environment.NewLine).Append(Environment.NewLine).Append(jsonPayload); - } - - this.logger.Write(LogLevel.Verbose, logStrBld.ToString()); - - string serializedMessage = - JsonConvert.SerializeObject( - messageObject, - Constants.JsonSerializerSettings); - - byte[] messageBytes = Encoding.UTF8.GetBytes(serializedMessage); - byte[] headerBytes = - Encoding.ASCII.GetBytes( - string.Format( - Constants.ContentLengthFormatString, - messageBytes.Length)); - - // Make sure only one call is writing at a time. You might be thinking - // "Why not use a normal lock?" We use an AsyncLock here so that the - // message loop doesn't get blocked while waiting for I/O to complete. - using (await this.writeLock.LockAsync()) - { - // Send the message - await this.outputStream.WriteAsync(headerBytes, 0, headerBytes.Length); - await this.outputStream.WriteAsync(messageBytes, 0, messageBytes.Length); - await this.outputStream.FlushAsync(); - } - } - - public async Task WriteRequestAsync( - RequestType requestType, - TParams requestParams, - int requestId) - { - // Allow null content - JToken contentObject = - requestParams != null ? - JToken.FromObject(requestParams, contentSerializer) : - null; - - await this.WriteMessageAsync( - Message.Request( - requestId.ToString(), - requestType.Method, - contentObject)); - } - - public async Task WriteResponseAsync(TResult resultContent, string method, string requestId) - { - // Allow null content - JToken contentObject = - resultContent != null ? - JToken.FromObject(resultContent, contentSerializer) : - null; - - await this.WriteMessageAsync( - Message.Response( - requestId, - method, - contentObject)); - } - - public async Task WriteEventAsync(NotificationType eventType, TParams eventParams) - { - // Allow null content - JToken contentObject = - eventParams != null ? - JToken.FromObject(eventParams, contentSerializer) : - null; - - await this.WriteMessageAsync( - Message.Event( - eventType.Method, - contentObject)); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs deleted file mode 100644 index ba52940a3..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs +++ /dev/null @@ -1,407 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - /// - /// Provides behavior for a client or server endpoint that - /// communicates using the specified protocol. - /// - public class ProtocolEndpoint : IMessageSender - { - private enum ProtocolEndpointState - { - NotStarted, - Started, - Shutdown - } - - private int currentMessageId; - private ChannelBase protocolChannel; - private ProtocolEndpointState currentState; - private IMessageDispatcher messageDispatcher; - private AsyncContextThread messageLoopThread; - private TaskCompletionSource endpointExitedTask; - private SynchronizationContext originalSynchronizationContext; - private CancellationTokenSource messageLoopCancellationToken = - new CancellationTokenSource(); - - private Dictionary> pendingRequests = - new Dictionary>(); - - public SynchronizationContext SynchronizationContext { get; private set; } - - private bool InMessageLoopThread - { - get - { - // We're in the same thread as the message loop if the - // current synchronization context equals the one we - // know. - return SynchronizationContext.Current == this.SynchronizationContext; - } - } - - protected ILogger Logger { get; private set; } - - /// - /// Initializes an instance of the protocol server using the - /// specified channel for communication. - /// - /// - /// The channel to use for communication with the connected endpoint. - /// - /// - /// The type of message protocol used by the endpoint. - /// - public ProtocolEndpoint( - ChannelBase protocolChannel, - IMessageDispatcher messageDispatcher, - ILogger logger) - { - this.protocolChannel = protocolChannel; - this.messageDispatcher = messageDispatcher; - this.Logger = logger; - - this.originalSynchronizationContext = SynchronizationContext.Current; - } - - /// - /// Starts the language server client and sends the Initialize method. - /// - public void Start() - { - if (this.currentState == ProtocolEndpointState.NotStarted) - { - // Listen for unhandled exceptions from the message loop - this.UnhandledException += MessageDispatcher_UnhandledException; - - // Start the message loop - this.StartMessageLoop(); - - // Endpoint is now started - this.currentState = ProtocolEndpointState.Started; - } - } - - public void WaitForExit() - { - this.endpointExitedTask = new TaskCompletionSource(); - this.endpointExitedTask.Task.Wait(); - } - - public void Stop() - { - if (this.currentState == ProtocolEndpointState.Started) - { - // Make sure no future calls try to stop the endpoint during shutdown - this.currentState = ProtocolEndpointState.Shutdown; - - // Stop the message loop and channel - this.StopMessageLoop(); - this.protocolChannel.Stop(); - - // Notify anyone waiting for exit - if (this.endpointExitedTask != null) - { - this.endpointExitedTask.SetResult(true); - } - } - } - - #region Message Sending - - public Task SendRequestAsync( - RequestType0 requestType0) - { - return this.SendRequestAsync( - RequestType.ConvertToRequestType(requestType0), - null); - } - - - /// - /// Sends a request to the server - /// - /// - /// - /// - /// - /// - public Task SendRequestAsync( - RequestType requestType, - TParams requestParams) - { - return this.SendRequestAsync(requestType, requestParams, true); - } - - public async Task SendRequestAsync( - RequestType requestType, - TParams requestParams, - bool waitForResponse) - { - // Some requests may still be in the SynchronizationContext queue - // after the server stops so don't act on those requests because - // the protocol channel will already be disposed - if (this.currentState == ProtocolEndpointState.Shutdown) - { - return default(TResult); - } - - this.currentMessageId++; - - TaskCompletionSource responseTask = null; - - if (waitForResponse) - { - responseTask = new TaskCompletionSource(); - this.pendingRequests.Add( - this.currentMessageId.ToString(), - responseTask); - } - - await this.protocolChannel.MessageWriter.WriteRequestAsync( - requestType, - requestParams, - this.currentMessageId); - - if (responseTask != null) - { - var responseMessage = await responseTask.Task; - - return - responseMessage.Contents != null ? - responseMessage.Contents.ToObject() : - default(TResult); - } - else - { - // TODO: Better default value here? - return default(TResult); - } - } - - /// - /// Sends an event to the channel's endpoint. - /// - /// The event parameter type. - /// The type of event being sent. - /// The event parameters being sent. - /// A Task that tracks completion of the send operation. - public Task SendEventAsync( - NotificationType eventType, - TParams eventParams) - { - // Some requests may still be in the SynchronizationContext queue - // after the server stops so don't act on those requests because - // the protocol channel will already be disposed - if (this.currentState == ProtocolEndpointState.Shutdown) - { - return Task.FromResult(true); - } - - // Some events could be raised from a different thread. - // To ensure that messages are written serially, dispatch - // dispatch the SendEvent call to the message loop thread. - - if (!this.InMessageLoopThread) - { - TaskCompletionSource writeTask = new TaskCompletionSource(); - - this.SynchronizationContext.Post( - async (obj) => - { - await this.protocolChannel.MessageWriter.WriteEventAsync( - eventType, - eventParams); - - writeTask.SetResult(true); - }, null); - - return writeTask.Task; - } - else - { - return this.protocolChannel.MessageWriter.WriteEventAsync( - eventType, - eventParams); - } - } - - #endregion - - #region Message Handling - - private void HandleResponse(Message responseMessage) - { - TaskCompletionSource pendingRequestTask = null; - - if (this.pendingRequests.TryGetValue(responseMessage.Id, out pendingRequestTask)) - { - pendingRequestTask.SetResult(responseMessage); - this.pendingRequests.Remove(responseMessage.Id); - } - } - - private void StartMessageLoop() - { - // Start the main message loop thread. The Task is - // not explicitly awaited because it is running on - // an independent background thread. - this.messageLoopThread = new AsyncContextThread("Message Dispatcher"); - this.messageLoopThread - .Run( - () => this.ListenForMessagesAsync(this.messageLoopCancellationToken.Token), - this.Logger) - .ContinueWith(this.OnListenTaskCompleted); - } - - private void StopMessageLoop() - { - // Stop the message loop thread - if (this.messageLoopThread != null) - { - this.messageLoopCancellationToken.Cancel(); - this.messageLoopThread.Stop(); - SynchronizationContext.SetSynchronizationContext(null); - } - } - - #endregion - - #region Events - - public event EventHandler UnhandledException; - - protected void OnUnhandledException(Exception unhandledException) - { - if (this.UnhandledException != null) - { - this.UnhandledException(this, unhandledException); - } - } - - #endregion - - #region Event Handlers - - private void MessageDispatcher_UnhandledException(object sender, Exception e) - { - if (this.endpointExitedTask != null) - { - this.endpointExitedTask.SetException(e); - } - else if (this.originalSynchronizationContext != null) - { - this.originalSynchronizationContext.Post(o => { throw e; }, null); - } - } - - #endregion - - #region Private Methods - - private async Task ListenForMessagesAsync(CancellationToken cancellationToken) - { - this.SynchronizationContext = SynchronizationContext.Current; - - // Run the message loop - bool isRunning = true; - while (isRunning && !cancellationToken.IsCancellationRequested) - { - Message newMessage = null; - - try - { - // Read a message from the channel - newMessage = await this.protocolChannel.MessageReader.ReadMessageAsync(); - } - catch (MessageParseException e) - { - // TODO: Write an error response - - Logger.Write( - LogLevel.Error, - "Could not parse a message that was received:\r\n\r\n" + - e.ToString()); - - // Continue the loop - continue; - } - catch (IOException e) - { - // The stream has ended, end the message loop - Logger.Write( - LogLevel.Error, - string.Format( - "Stream terminated unexpectedly, ending MessageDispatcher loop\r\n\r\nException: {0}\r\n{1}", - e.GetType().Name, - e.Message)); - - break; - } - catch (ObjectDisposedException) - { - Logger.Write( - LogLevel.Verbose, - "MessageReader attempted to read from a disposed stream, ending MessageDispatcher loop"); - - break; - } - catch (Exception e) - { - Logger.WriteException( - "Caught unhandled exception in ProtocolEndpoint message loop", - e); - } - - // The message could be null if there was an error parsing the - // previous message. In this case, do not try to dispatch it. - if (newMessage != null) - { - if (newMessage.MessageType == MessageType.Response) - { - this.HandleResponse(newMessage); - } - else - { - // Process the message - await this.messageDispatcher.DispatchMessageAsync( - newMessage, - this.protocolChannel.MessageWriter); - } - } - } - } - - private void OnListenTaskCompleted(Task listenTask) - { - if (listenTask.IsFaulted) - { - Logger.Write( - LogLevel.Error, - string.Format( - "ProtocolEndpoint message loop terminated due to unhandled exception:\r\n\r\n{0}", - listenTask.Exception.ToString())); - - this.OnUnhandledException(listenTask.Exception); - } - else if (listenTask.IsCompleted || listenTask.IsCanceled) - { - // TODO: Dispose of anything? - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestContext.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestContext.cs deleted file mode 100644 index 94c9264b7..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestContext.cs +++ /dev/null @@ -1,47 +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 Newtonsoft.Json.Linq; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - public class RequestContext - { - private Message requestMessage; - private MessageWriter messageWriter; - - public RequestContext(Message requestMessage, MessageWriter messageWriter) - { - this.requestMessage = requestMessage; - this.messageWriter = messageWriter; - } - - public async Task SendResultAsync(TResult resultDetails) - { - await this.messageWriter.WriteResponseAsync( - resultDetails, - requestMessage.Method, - requestMessage.Id); - } - - public async Task SendEventAsync(NotificationType eventType, TParams eventParams) - { - await this.messageWriter.WriteEventAsync( - eventType, - eventParams); - } - - public async Task SendErrorAsync(object errorDetails) - { - await this.messageWriter.WriteMessageAsync( - Message.ResponseError( - requestMessage.Id, - requestMessage.Method, - JToken.FromObject(errorDetails))); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestType.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestType.cs deleted file mode 100644 index d7c5a6e8e..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestType.cs +++ /dev/null @@ -1,35 +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.Diagnostics; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - [DebuggerDisplay("RequestType Method = {Method}")] - public class RequestType : AbstractMessageType - { - private RequestType(string method) : base(method, 1) - { - - } - - public static RequestType ConvertToRequestType( - RequestType0 requestType0) - { - return RequestType.Create(requestType0.Method); - } - - public static RequestType Create(string method) - { - if (method == null) - { - throw new System.ArgumentNullException(nameof(method)); - } - - return new RequestType(method); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestType0.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestType0.cs deleted file mode 100644 index e8fc8da1c..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/RequestType0.cs +++ /dev/null @@ -1,22 +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.Diagnostics; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol -{ - [DebuggerDisplay("RequestType0 Method = {Method}")] - public class RequestType0 : AbstractMessageType - { - public RequestType0(string method) : base(method, 0) - { - } - - public static RequestType0 Create(string method) - { - return new RequestType0(method); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Serializers/JsonRpcMessageSerializer.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Serializers/JsonRpcMessageSerializer.cs deleted file mode 100644 index 8f736ab01..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Serializers/JsonRpcMessageSerializer.cs +++ /dev/null @@ -1,102 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers -{ - /// - /// Serializes messages in the JSON RPC format. Used primarily - /// for language servers. - /// - public class JsonRpcMessageSerializer : IMessageSerializer - { - public JObject SerializeMessage(Message message) - { - JObject messageObject = new JObject(); - - messageObject.Add("jsonrpc", JToken.FromObject("2.0")); - - if (message.MessageType == MessageType.Request) - { - messageObject.Add("id", JToken.FromObject(message.Id)); - messageObject.Add("method", message.Method); - messageObject.Add("params", message.Contents); - } - else if (message.MessageType == MessageType.Event) - { - messageObject.Add("method", message.Method); - messageObject.Add("params", message.Contents); - } - else if (message.MessageType == MessageType.Response) - { - messageObject.Add("id", JToken.FromObject(message.Id)); - - if (message.Error != null) - { - // Write error - messageObject.Add("error", message.Error); - } - else - { - // Write result - messageObject.Add("result", message.Contents); - } - } - - return messageObject; - } - - public Message DeserializeMessage(JObject messageJson) - { - // TODO: Check for jsonrpc version - - JToken token = null; - if (messageJson.TryGetValue("id", out token)) - { - // Message is a Request or Response - string messageId = token.ToString(); - - if (messageJson.TryGetValue("result", out token)) - { - return Message.Response(messageId, null, token); - } - else if (messageJson.TryGetValue("error", out token)) - { - return Message.ResponseError(messageId, null, token); - } - else - { - JToken messageParams = null; - messageJson.TryGetValue("params", out messageParams); - - if (!messageJson.TryGetValue("method", out token)) - { - // TODO: Throw parse error - } - - return Message.Request(messageId, token.ToString(), messageParams); - } - } - else - { - // Messages without an id are events - JToken messageParams = token; - messageJson.TryGetValue("params", out messageParams); - - if (!messageJson.TryGetValue("method", out token)) - { - // TODO: Throw parse error - } - - return Message.Event(token.ToString(), messageParams); - } - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Serializers/V8MessageSerializer.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Serializers/V8MessageSerializer.cs deleted file mode 100644 index dd21bc367..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Serializers/V8MessageSerializer.cs +++ /dev/null @@ -1,118 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; - -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers -{ - /// - /// Serializes messages in the V8 format. Used primarily for debug adapters. - /// - public class V8MessageSerializer : IMessageSerializer - { - public JObject SerializeMessage(Message message) - { - JObject messageObject = new JObject(); - - if (message.MessageType == MessageType.Request) - { - messageObject.Add("type", JToken.FromObject("request")); - messageObject.Add("seq", JToken.FromObject(message.Id)); - messageObject.Add("command", message.Method); - messageObject.Add("arguments", message.Contents); - } - else if (message.MessageType == MessageType.Event) - { - messageObject.Add("type", JToken.FromObject("event")); - messageObject.Add("event", message.Method); - messageObject.Add("body", message.Contents); - } - else if (message.MessageType == MessageType.Response) - { - int messageId = 0; - int.TryParse(message.Id, out messageId); - - messageObject.Add("type", JToken.FromObject("response")); - messageObject.Add("request_seq", JToken.FromObject(messageId)); - messageObject.Add("command", message.Method); - - if (message.Error != null) - { - // Write error - messageObject.Add("success", JToken.FromObject(false)); - messageObject.Add("message", message.Error); - } - else - { - // Write result - messageObject.Add("success", JToken.FromObject(true)); - messageObject.Add("body", message.Contents); - } - } - - return messageObject; - } - - public Message DeserializeMessage(JObject messageJson) - { - JToken token = null; - - if (messageJson.TryGetValue("type", out token)) - { - string messageType = token.ToString(); - - if (string.Equals("request", messageType, StringComparison.CurrentCultureIgnoreCase)) - { - return Message.Request( - messageJson.GetValue("seq").ToString(), - messageJson.GetValue("command").ToString(), - messageJson.GetValue("arguments")); - } - else if (string.Equals("response", messageType, StringComparison.CurrentCultureIgnoreCase)) - { - if (messageJson.TryGetValue("success", out token)) - { - // Was the response for a successful request? - if (token.ToObject() == true) - { - return Message.Response( - messageJson.GetValue("request_seq").ToString(), - messageJson.GetValue("command").ToString(), - messageJson.GetValue("body")); - } - else - { - return Message.ResponseError( - messageJson.GetValue("request_seq").ToString(), - messageJson.GetValue("command").ToString(), - messageJson.GetValue("message")); - } - } - else - { - // TODO: Parse error - } - - } - else if (string.Equals("event", messageType, StringComparison.CurrentCultureIgnoreCase)) - { - return Message.Event( - messageJson.GetValue("event").ToString(), - messageJson.GetValue("body")); - } - else - { - return Message.Unknown(); - } - } - - return Message.Unknown(); - } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs b/src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs deleted file mode 100644 index 3a04eaf1e..000000000 --- a/src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs +++ /dev/null @@ -1,58 +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 Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Messages -{ - public class ShowChoicePromptRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/showChoicePrompt"); - - public bool IsMultiChoice { get; set; } - - public string Caption { get; set; } - - public string Message { get; set; } - - public ChoiceDetails[] Choices { get; set; } - - public int[] DefaultChoices { get; set; } - } - - public class ShowChoicePromptResponse - { - public bool PromptCancelled { get; set; } - - public string ResponseText { get; set; } - } - - public class ShowInputPromptRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/showInputPrompt"); - - /// - /// Gets or sets the name of the field. - /// - public string Name { get; set; } - - /// - /// Gets or sets the descriptive label for the field. - /// - public string Label { get; set; } - } - - public class ShowInputPromptResponse - { - public bool PromptCancelled { get; set; } - - public string ResponseText { get; set; } - } -} - diff --git a/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj b/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj deleted file mode 100644 index 6857c083c..000000000 --- a/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - PowerShell Editor Services Host Protocol Library - Provides message types and client/server APIs for the PowerShell Editor Services JSON protocol. - netstandard2.0 - Microsoft.PowerShell.EditorServices.Protocol - - - - - - - - - - - diff --git a/src/PowerShellEditorServices.Protocol/Properties/AssemblyInfo.cs b/src/PowerShellEditorServices.Protocol/Properties/AssemblyInfo.cs deleted file mode 100644 index 790e19d63..000000000 --- a/src/PowerShellEditorServices.Protocol/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,8 +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.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.PowerShell.EditorServices.Test.Protocol")] \ No newline at end of file diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs deleted file mode 100644 index 25ac3c81a..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ /dev/null @@ -1,1159 +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 Microsoft.PowerShell.EditorServices.Debugging; -using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Security; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - public class DebugAdapter - { - private static readonly Version _minVersionForCustomPipeName = new Version(6, 2); - - private EditorSession _editorSession; - - private bool _noDebug; - private ILogger Logger; - private string _arguments; - private bool _isRemoteAttach; - private bool _isAttachSession; - private bool _waitingForAttach; - private string _scriptToLaunch; - private bool _ownsEditorSession; - private bool _executionCompleted; - private IMessageSender _messageSender; - private IMessageHandlers _messageHandlers; - private bool _isInteractiveDebugSession; - private bool _setBreakpointInProgress; - private RequestContext _disconnectRequestContext = null; - - public DebugAdapter( - EditorSession editorSession, - bool ownsEditorSession, - IMessageHandlers messageHandlers, - IMessageSender messageSender, - ILogger logger) - { - Logger = logger; - _editorSession = editorSession; - _messageSender = messageSender; - _messageHandlers = messageHandlers; - _ownsEditorSession = ownsEditorSession; - } - - /// - /// Gets a boolean that indicates whether the current debug adapter is - /// using a temporary integrated console. - /// - public bool IsUsingTempIntegratedConsole { get; private set; } - - public void Start() - { - // Register all supported message types - _messageHandlers.SetRequestHandler(InitializeRequest.Type, HandleInitializeRequestAsync); - - _messageHandlers.SetRequestHandler(LaunchRequest.Type, HandleLaunchRequestAsync); - _messageHandlers.SetRequestHandler(AttachRequest.Type, HandleAttachRequestAsync); - _messageHandlers.SetRequestHandler(ConfigurationDoneRequest.Type, HandleConfigurationDoneRequestAsync); - _messageHandlers.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequestAsync); - - _messageHandlers.SetRequestHandler(SetBreakpointsRequest.Type, HandleSetBreakpointsRequestAsync); - _messageHandlers.SetRequestHandler(SetExceptionBreakpointsRequest.Type, HandleSetExceptionBreakpointsRequestAsync); - _messageHandlers.SetRequestHandler(SetFunctionBreakpointsRequest.Type, HandleSetFunctionBreakpointsRequestAsync); - - _messageHandlers.SetRequestHandler(ContinueRequest.Type, HandleContinueRequestAsync); - _messageHandlers.SetRequestHandler(NextRequest.Type, HandleNextRequestAsync); - _messageHandlers.SetRequestHandler(StepInRequest.Type, HandleStepInRequestAsync); - _messageHandlers.SetRequestHandler(StepOutRequest.Type, HandleStepOutRequestAsync); - _messageHandlers.SetRequestHandler(PauseRequest.Type, HandlePauseRequestAsync); - - _messageHandlers.SetRequestHandler(ThreadsRequest.Type, HandleThreadsRequestAsync); - _messageHandlers.SetRequestHandler(StackTraceRequest.Type, HandleStackTraceRequestAsync); - _messageHandlers.SetRequestHandler(ScopesRequest.Type, HandleScopesRequestAsync); - _messageHandlers.SetRequestHandler(VariablesRequest.Type, HandleVariablesRequestAsync); - _messageHandlers.SetRequestHandler(SetVariableRequest.Type, HandleSetVariablesRequestAsync); - _messageHandlers.SetRequestHandler(SourceRequest.Type, HandleSourceRequestAsync); - _messageHandlers.SetRequestHandler(EvaluateRequest.Type, HandleEvaluateRequestAsync); - } - - protected Task LaunchScriptAsync(RequestContext requestContext, string scriptToLaunch) - { - // Is this an untitled script? - Task launchTask = null; - - if (ScriptFile.IsUntitledPath(scriptToLaunch)) - { - ScriptFile untitledScript = _editorSession.Workspace.GetFile(scriptToLaunch); - - launchTask = _editorSession.PowerShellContext - .ExecuteScriptStringAsync(untitledScript.Contents, true, true); - } - else - { - launchTask = _editorSession.PowerShellContext - .ExecuteScriptWithArgsAsync(scriptToLaunch, _arguments, writeInputToHost: true); - } - - return launchTask.ContinueWith(OnExecutionCompletedAsync); - } - - private async Task OnExecutionCompletedAsync(Task executeTask) - { - try - { - await executeTask; - } - catch (Exception e) - { - Logger.Write( - LogLevel.Error, - "Exception occurred while awaiting debug launch task.\n\n" + e.ToString()); - } - - Logger.Write(LogLevel.Verbose, "Execution completed, terminating..."); - - _executionCompleted = true; - - UnregisterEventHandlers(); - - if (_isAttachSession) - { - // Pop the sessions - if (_editorSession.PowerShellContext.CurrentRunspace.Context == RunspaceContext.EnteredProcess) - { - try - { - await _editorSession.PowerShellContext.ExecuteScriptStringAsync("Exit-PSHostProcess"); - - if (_isRemoteAttach && - _editorSession.PowerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote) - { - await _editorSession.PowerShellContext.ExecuteScriptStringAsync("Exit-PSSession"); - } - } - catch (Exception e) - { - Logger.WriteException("Caught exception while popping attached process after debugging", e); - } - } - } - - _editorSession.DebugService.IsClientAttached = false; - - if (_disconnectRequestContext != null) - { - // Respond to the disconnect request and stop the server - await _disconnectRequestContext.SendResultAsync(null); - Stop(); - return; - } - - await _messageSender.SendEventAsync( - TerminatedEvent.Type, - new TerminatedEvent()); - } - - protected void Stop() - { - Logger.Write(LogLevel.Normal, "Debug adapter is shutting down..."); - - if (_editorSession != null) - { - _editorSession.PowerShellContext.RunspaceChanged -= powerShellContext_RunspaceChangedAsync; - _editorSession.DebugService.DebuggerStopped -= DebugService_DebuggerStoppedAsync; - _editorSession.PowerShellContext.DebuggerResumed -= powerShellContext_DebuggerResumedAsync; - - if (_ownsEditorSession) - { - _editorSession.Dispose(); - } - - _editorSession = null; - } - - OnSessionEnded(); - } - - #region Built-in Message Handlers - - private async Task HandleInitializeRequestAsync( - object shutdownParams, - RequestContext requestContext) - { - // Clear any existing breakpoints before proceeding - await ClearSessionBreakpointsAsync(); - - // Now send the Initialize response to continue setup - await requestContext.SendResultAsync( - new InitializeResponseBody { - SupportsConfigurationDoneRequest = true, - SupportsFunctionBreakpoints = true, - SupportsConditionalBreakpoints = true, - SupportsHitConditionalBreakpoints = true, - SupportsSetVariable = true - }); - } - - protected async Task HandleConfigurationDoneRequestAsync( - object args, - RequestContext requestContext) - { - _editorSession.DebugService.IsClientAttached = true; - - if (!string.IsNullOrEmpty(_scriptToLaunch)) - { - if (_editorSession.PowerShellContext.SessionState == PowerShellContextState.Ready) - { - // Configuration is done, launch the script - var nonAwaitedTask = LaunchScriptAsync(requestContext, _scriptToLaunch) - .ConfigureAwait(continueOnCapturedContext: false); - } - else - { - Logger.Write( - LogLevel.Verbose, - "configurationDone request called after script was already launched, skipping it."); - } - } - - await requestContext.SendResultAsync(null); - - if (_isInteractiveDebugSession) - { - if (_ownsEditorSession) - { - // If this is a debug-only session, we need to start - // the command loop manually - _editorSession.HostInput.StartCommandLoop(); - } - - if (_editorSession.DebugService.IsDebuggerStopped) - { - // If this is an interactive session and there's a pending breakpoint, - // send that information along to the debugger client - DebugService_DebuggerStoppedAsync( - this, - _editorSession.DebugService.CurrentDebuggerStoppedEventArgs); - } - } - } - - protected async Task HandleLaunchRequestAsync( - LaunchRequestArguments launchParams, - RequestContext requestContext) - { - RegisterEventHandlers(); - - // Determine whether or not the working directory should be set in the PowerShellContext. - if ((_editorSession.PowerShellContext.CurrentRunspace.Location == RunspaceLocation.Local) && - !_editorSession.DebugService.IsDebuggerStopped) - { - // Get the working directory that was passed via the debug config - // (either via launch.json or generated via no-config debug). - string workingDir = launchParams.Cwd; - - // Assuming we have a non-empty/null working dir, unescape the path and verify - // the path exists and is a directory. - if (!string.IsNullOrEmpty(workingDir)) - { - try - { - if ((File.GetAttributes(workingDir) & FileAttributes.Directory) != FileAttributes.Directory) - { - workingDir = Path.GetDirectoryName(workingDir); - } - } - catch (Exception ex) - { - workingDir = null; - Logger.Write( - LogLevel.Error, - $"The specified 'cwd' path is invalid: '{launchParams.Cwd}'. Error: {ex.Message}"); - } - } - - // If we have no working dir by this point and we are running in a temp console, - // pick some reasonable default. - if (string.IsNullOrEmpty(workingDir) && launchParams.CreateTemporaryIntegratedConsole) - { - workingDir = Environment.CurrentDirectory; - } - - // At this point, we will either have a working dir that should be set to cwd in - // the PowerShellContext or the user has requested (via an empty/null cwd) that - // the working dir should not be changed. - if (!string.IsNullOrEmpty(workingDir)) - { - await _editorSession.PowerShellContext.SetWorkingDirectoryAsync(workingDir, isPathAlreadyEscaped: false); - } - - Logger.Write(LogLevel.Verbose, $"Working dir " + (string.IsNullOrEmpty(workingDir) ? "not set." : $"set to '{workingDir}'")); - } - - // Prepare arguments to the script - if specified - string arguments = null; - if ((launchParams.Args != null) && (launchParams.Args.Length > 0)) - { - arguments = string.Join(" ", launchParams.Args); - Logger.Write(LogLevel.Verbose, "Script arguments are: " + arguments); - } - - // Store the launch parameters so that they can be used later - _noDebug = launchParams.NoDebug; - _scriptToLaunch = launchParams.Script; - _arguments = arguments; - IsUsingTempIntegratedConsole = launchParams.CreateTemporaryIntegratedConsole; - - // If the current session is remote, map the script path to the remote - // machine if necessary - if (_scriptToLaunch != null && - _editorSession.PowerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote) - { - _scriptToLaunch = - _editorSession.RemoteFileManager.GetMappedPath( - _scriptToLaunch, - _editorSession.PowerShellContext.CurrentRunspace); - } - - await requestContext.SendResultAsync(null); - - // If no script is being launched, mark this as an interactive - // debugging session - _isInteractiveDebugSession = string.IsNullOrEmpty(_scriptToLaunch); - - // Send the InitializedEvent so that the debugger will continue - // sending configuration requests - await _messageSender.SendEventAsync( - InitializedEvent.Type, - null); - } - - protected async Task HandleAttachRequestAsync( - AttachRequestArguments attachParams, - RequestContext requestContext) - { - _isAttachSession = true; - - RegisterEventHandlers(); - - bool processIdIsSet = !string.IsNullOrEmpty(attachParams.ProcessId) && attachParams.ProcessId != "undefined"; - bool customPipeNameIsSet = !string.IsNullOrEmpty(attachParams.CustomPipeName) && attachParams.CustomPipeName != "undefined"; - - PowerShellVersionDetails runspaceVersion = - _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion; - - // If there are no host processes to attach to or the user cancels selection, we get a null for the process id. - // This is not an error, just a request to stop the original "attach to" request. - // Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading - // to cancel on the VSCode side without sending an attachRequest with processId set to "undefined". - if (!processIdIsSet && !customPipeNameIsSet) - { - Logger.Write( - LogLevel.Normal, - $"Attach request aborted, received {attachParams.ProcessId} for processId."); - - await requestContext.SendErrorAsync( - "User aborted attach to PowerShell host process."); - - return; - } - - StringBuilder errorMessages = new StringBuilder(); - - if (attachParams.ComputerName != null) - { - if (runspaceVersion.Version.Major < 4) - { - await requestContext.SendErrorAsync( - $"Remote sessions are only available with PowerShell 4 and higher (current session is {runspaceVersion.Version})."); - - return; - } - else if (_editorSession.PowerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote) - { - await requestContext.SendErrorAsync( - $"Cannot attach to a process in a remote session when already in a remote session."); - - return; - } - - await _editorSession.PowerShellContext.ExecuteScriptStringAsync( - $"Enter-PSSession -ComputerName \"{attachParams.ComputerName}\"", - errorMessages); - - if (errorMessages.Length > 0) - { - await requestContext.SendErrorAsync( - $"Could not establish remote session to computer '{attachParams.ComputerName}'"); - - return; - } - - _isRemoteAttach = true; - } - - if (processIdIsSet && int.TryParse(attachParams.ProcessId, out int processId) && (processId > 0)) - { - if (runspaceVersion.Version.Major < 5) - { - await requestContext.SendErrorAsync( - $"Attaching to a process is only available with PowerShell 5 and higher (current session is {runspaceVersion.Version})."); - return; - } - - await _editorSession.PowerShellContext.ExecuteScriptStringAsync( - $"Enter-PSHostProcess -Id {processId}", - errorMessages); - - if (errorMessages.Length > 0) - { - await requestContext.SendErrorAsync( - $"Could not attach to process '{processId}'"); - - return; - } - } - else if (customPipeNameIsSet) - { - if (runspaceVersion.Version < _minVersionForCustomPipeName) - { - await requestContext.SendErrorAsync( - $"Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is {runspaceVersion.Version})."); - return; - } - - await _editorSession.PowerShellContext.ExecuteScriptStringAsync( - $"Enter-PSHostProcess -CustomPipeName {attachParams.CustomPipeName}", - errorMessages); - - if (errorMessages.Length > 0) - { - await requestContext.SendErrorAsync( - $"Could not attach to process with CustomPipeName: '{attachParams.CustomPipeName}'"); - - return; - } - } - else if (attachParams.ProcessId != "current") - { - Logger.Write( - LogLevel.Error, - $"Attach request failed, '{attachParams.ProcessId}' is an invalid value for the processId."); - - await requestContext.SendErrorAsync( - "A positive integer must be specified for the processId field."); - - return; - } - - // Clear any existing breakpoints before proceeding - await ClearSessionBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false); - - // Execute the Debug-Runspace command but don't await it because it - // will block the debug adapter initialization process. The - // InitializedEvent will be sent as soon as the RunspaceChanged - // event gets fired with the attached runspace. - - string debugRunspaceCmd; - if (attachParams.RunspaceName != null) - { - debugRunspaceCmd = $"\nDebug-Runspace -Name '{attachParams.RunspaceName}'"; - } - else if (attachParams.RunspaceId != null) - { - if (!int.TryParse(attachParams.RunspaceId, out int runspaceId) || runspaceId <= 0) - { - Logger.Write( - LogLevel.Error, - $"Attach request failed, '{attachParams.RunspaceId}' is an invalid value for the processId."); - - await requestContext.SendErrorAsync( - "A positive integer must be specified for the RunspaceId field."); - - return; - } - - debugRunspaceCmd = $"\nDebug-Runspace -Id {runspaceId}"; - } - else - { - debugRunspaceCmd = "\nDebug-Runspace -Id 1"; - } - - _waitingForAttach = true; - Task nonAwaitedTask = _editorSession.PowerShellContext - .ExecuteScriptStringAsync(debugRunspaceCmd) - .ContinueWith(OnExecutionCompletedAsync); - - await requestContext.SendResultAsync(null); - } - - protected async Task HandleDisconnectRequestAsync( - object disconnectParams, - RequestContext requestContext) - { - // In some rare cases, the EditorSession will already be disposed - // so we shouldn't try to abort because PowerShellContext will be null - if (_editorSession != null && _editorSession.PowerShellContext != null) - { - if (_executionCompleted == false) - { - _disconnectRequestContext = requestContext; - _editorSession.PowerShellContext.AbortExecution(shouldAbortDebugSession: true); - - if (_isInteractiveDebugSession) - { - await OnExecutionCompletedAsync(null); - } - } - else - { - UnregisterEventHandlers(); - - await requestContext.SendResultAsync(null); - Stop(); - } - } - } - - protected async Task HandleSetBreakpointsRequestAsync( - SetBreakpointsRequestArguments setBreakpointsParams, - RequestContext requestContext) - { - ScriptFile scriptFile = null; - - // When you set a breakpoint in the right pane of a Git diff window on a PS1 file, - // the Source.Path comes through as Untitled-X. That's why we check for IsUntitledPath. - if (!ScriptFile.IsUntitledPath(setBreakpointsParams.Source.Path) && - !_editorSession.Workspace.TryGetFile( - setBreakpointsParams.Source.Path, - out scriptFile)) - { - string message = _noDebug ? string.Empty : "Source file could not be accessed, breakpoint not set."; - var srcBreakpoints = setBreakpointsParams.Breakpoints - .Select(srcBkpt => Protocol.DebugAdapter.Breakpoint.Create( - srcBkpt, setBreakpointsParams.Source.Path, message, verified: _noDebug)); - - // Return non-verified breakpoint message. - await requestContext.SendResultAsync( - new SetBreakpointsResponseBody { - Breakpoints = srcBreakpoints.ToArray() - }); - - return; - } - - // Verify source file is a PowerShell script file. - string fileExtension = Path.GetExtension(scriptFile?.FilePath ?? "")?.ToLower(); - if (string.IsNullOrEmpty(fileExtension) || ((fileExtension != ".ps1") && (fileExtension != ".psm1"))) - { - Logger.Write( - LogLevel.Warning, - $"Attempted to set breakpoints on a non-PowerShell file: {setBreakpointsParams.Source.Path}"); - - string message = _noDebug ? string.Empty : "Source is not a PowerShell script, breakpoint not set."; - - var srcBreakpoints = setBreakpointsParams.Breakpoints - .Select(srcBkpt => Protocol.DebugAdapter.Breakpoint.Create( - srcBkpt, setBreakpointsParams.Source.Path, message, verified: _noDebug)); - - // Return non-verified breakpoint message. - await requestContext.SendResultAsync( - new SetBreakpointsResponseBody - { - Breakpoints = srcBreakpoints.ToArray() - }); - - return; - } - - // At this point, the source file has been verified as a PowerShell script. - var breakpointDetails = new BreakpointDetails[setBreakpointsParams.Breakpoints.Length]; - for (int i = 0; i < breakpointDetails.Length; i++) - { - SourceBreakpoint srcBreakpoint = setBreakpointsParams.Breakpoints[i]; - breakpointDetails[i] = BreakpointDetails.Create( - scriptFile.FilePath, - srcBreakpoint.Line, - srcBreakpoint.Column, - srcBreakpoint.Condition, - srcBreakpoint.HitCondition); - } - - // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. - BreakpointDetails[] updatedBreakpointDetails = breakpointDetails; - if (!_noDebug) - { - _setBreakpointInProgress = true; - - try - { - updatedBreakpointDetails = - await _editorSession.DebugService.SetLineBreakpointsAsync( - scriptFile, - breakpointDetails); - } - catch (Exception e) - { - // Log whatever the error is - Logger.WriteException($"Caught error while setting breakpoints in SetBreakpoints handler for file {scriptFile?.FilePath}", e); - } - finally - { - _setBreakpointInProgress = false; - } - } - - await requestContext.SendResultAsync( - new SetBreakpointsResponseBody { - Breakpoints = - updatedBreakpointDetails - .Select(Protocol.DebugAdapter.Breakpoint.Create) - .ToArray() - }); - } - - protected async Task HandleSetFunctionBreakpointsRequestAsync( - SetFunctionBreakpointsRequestArguments setBreakpointsParams, - RequestContext requestContext) - { - var breakpointDetails = new CommandBreakpointDetails[setBreakpointsParams.Breakpoints.Length]; - for (int i = 0; i < breakpointDetails.Length; i++) - { - FunctionBreakpoint funcBreakpoint = setBreakpointsParams.Breakpoints[i]; - breakpointDetails[i] = CommandBreakpointDetails.Create( - funcBreakpoint.Name, - funcBreakpoint.Condition, - funcBreakpoint.HitCondition); - } - - // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. - CommandBreakpointDetails[] updatedBreakpointDetails = breakpointDetails; - if (!_noDebug) - { - _setBreakpointInProgress = true; - - try - { - updatedBreakpointDetails = - await _editorSession.DebugService.SetCommandBreakpointsAsync( - breakpointDetails); - } - catch (Exception e) - { - // Log whatever the error is - Logger.WriteException($"Caught error while setting command breakpoints", e); - } - finally - { - _setBreakpointInProgress = false; - } - } - - await requestContext.SendResultAsync( - new SetBreakpointsResponseBody { - Breakpoints = - updatedBreakpointDetails - .Select(Protocol.DebugAdapter.Breakpoint.Create) - .ToArray() - }); - } - - protected async Task HandleSetExceptionBreakpointsRequestAsync( - SetExceptionBreakpointsRequestArguments setExceptionBreakpointsParams, - RequestContext requestContext) - { - // TODO: When support for exception breakpoints (unhandled and/or first chance) - // are added to the PowerShell engine, wire up the VSCode exception - // breakpoints here using the pattern below to prevent bug regressions. - //if (!noDebug) - //{ - // setBreakpointInProgress = true; - - // try - // { - // // Set exception breakpoints in DebugService - // } - // catch (Exception e) - // { - // // Log whatever the error is - // Logger.WriteException($"Caught error while setting exception breakpoints", e); - // } - // finally - // { - // setBreakpointInProgress = false; - // } - //} - - await requestContext.SendResultAsync(null); - } - - protected async Task HandleContinueRequestAsync( - object continueParams, - RequestContext requestContext) - { - _editorSession.DebugService.Continue(); - - await requestContext.SendResultAsync(null); - } - - protected async Task HandleNextRequestAsync( - object nextParams, - RequestContext requestContext) - { - _editorSession.DebugService.StepOver(); - - await requestContext.SendResultAsync(null); - } - - protected Task HandlePauseRequestAsync( - object pauseParams, - RequestContext requestContext) - { - try - { - _editorSession.DebugService.Break(); - } - catch (NotSupportedException e) - { - return requestContext.SendErrorAsync(e.Message); - } - - // This request is responded to by sending the "stopped" event - return Task.FromResult(true); - } - - protected async Task HandleStepInRequestAsync( - object stepInParams, - RequestContext requestContext) - { - _editorSession.DebugService.StepIn(); - - await requestContext.SendResultAsync(null); - } - - protected async Task HandleStepOutRequestAsync( - object stepOutParams, - RequestContext requestContext) - { - _editorSession.DebugService.StepOut(); - - await requestContext.SendResultAsync(null); - } - - protected async Task HandleThreadsRequestAsync( - object threadsParams, - RequestContext requestContext) - { - await requestContext.SendResultAsync( - new ThreadsResponseBody - { - Threads = new Thread[] - { - // TODO: What do I do with these? - new Thread - { - Id = 1, - Name = "Main Thread" - } - } - }); - } - - protected async Task HandleStackTraceRequestAsync( - StackTraceRequestArguments stackTraceParams, - RequestContext requestContext) - { - StackFrameDetails[] stackFrames = - _editorSession.DebugService.GetStackFrames(); - - // Handle a rare race condition where the adapter requests stack frames before they've - // begun building. - if (stackFrames == null) - { - await requestContext.SendResultAsync( - new StackTraceResponseBody - { - StackFrames = new StackFrame[0], - TotalFrames = 0 - }); - - return; - } - - List newStackFrames = new List(); - - int startFrameIndex = stackTraceParams.StartFrame ?? 0; - int maxFrameCount = stackFrames.Length; - - // If the number of requested levels == 0 (or null), that means get all stack frames - // after the specified startFrame index. Otherwise get all the stack frames. - int requestedFrameCount = (stackTraceParams.Levels ?? 0); - if (requestedFrameCount > 0) - { - maxFrameCount = Math.Min(maxFrameCount, startFrameIndex + requestedFrameCount); - } - - for (int i = startFrameIndex; i < maxFrameCount; i++) - { - // Create the new StackFrame object with an ID that can - // be referenced back to the current list of stack frames - newStackFrames.Add( - StackFrame.Create( - stackFrames[i], - i)); - } - - await requestContext.SendResultAsync( - new StackTraceResponseBody - { - StackFrames = newStackFrames.ToArray(), - TotalFrames = newStackFrames.Count - }); - } - - protected async Task HandleScopesRequestAsync( - ScopesRequestArguments scopesParams, - RequestContext requestContext) - { - VariableScope[] variableScopes = - _editorSession.DebugService.GetVariableScopes( - scopesParams.FrameId); - - await requestContext.SendResultAsync( - new ScopesResponseBody - { - Scopes = - variableScopes - .Select(Scope.Create) - .ToArray() - }); - } - - protected async Task HandleVariablesRequestAsync( - VariablesRequestArguments variablesParams, - RequestContext requestContext) - { - VariableDetailsBase[] variables = - _editorSession.DebugService.GetVariables( - variablesParams.VariablesReference); - - VariablesResponseBody variablesResponse = null; - - try - { - variablesResponse = new VariablesResponseBody - { - Variables = - variables - .Select(Variable.Create) - .ToArray() - }; - } - catch (Exception) - { - // TODO: This shouldn't be so broad - } - - await requestContext.SendResultAsync(variablesResponse); - } - - protected async Task HandleSetVariablesRequestAsync( - SetVariableRequestArguments setVariableParams, - RequestContext requestContext) - { - try - { - string updatedValue = - await _editorSession.DebugService.SetVariableAsync( - setVariableParams.VariablesReference, - setVariableParams.Name, - setVariableParams.Value); - - var setVariableResponse = new SetVariableResponseBody - { - Value = updatedValue - }; - - await requestContext.SendResultAsync(setVariableResponse); - } - catch (Exception ex) when (ex is ArgumentTransformationMetadataException || - ex is InvalidPowerShellExpressionException || - ex is SessionStateUnauthorizedAccessException) - { - // Catch common, innocuous errors caused by the user supplying a value that can't be converted or the variable is not settable. - Logger.Write(LogLevel.Verbose, $"Failed to set variable: {ex.Message}"); - await requestContext.SendErrorAsync(ex.Message); - } - catch (Exception ex) - { - Logger.Write(LogLevel.Error, $"Unexpected error setting variable: {ex.Message}"); - string msg = - $"Unexpected error: {ex.GetType().Name} - {ex.Message} Please report this error to the PowerShellEditorServices project on GitHub."; - await requestContext.SendErrorAsync(msg); - } - } - - protected Task HandleSourceRequestAsync( - SourceRequestArguments sourceParams, - RequestContext requestContext) - { - // TODO: Implement this message. For now, doesn't seem to - // be a problem that it's missing. - - return Task.FromResult(true); - } - - protected async Task HandleEvaluateRequestAsync( - EvaluateRequestArguments evaluateParams, - RequestContext requestContext) - { - string valueString = null; - int variableId = 0; - - bool isFromRepl = - string.Equals( - evaluateParams.Context, - "repl", - StringComparison.CurrentCultureIgnoreCase); - - if (isFromRepl) - { - var notAwaited = - _editorSession - .PowerShellContext - .ExecuteScriptStringAsync(evaluateParams.Expression, false, true) - .ConfigureAwait(false); - } - else - { - VariableDetailsBase result = null; - - // VS Code might send this request after the debugger - // has been resumed, return an empty result in this case. - if (_editorSession.PowerShellContext.IsDebuggerStopped) - { - // First check to see if the watch expression refers to a naked variable reference. - result = - _editorSession.DebugService.GetVariableFromExpression(evaluateParams.Expression, evaluateParams.FrameId); - - // If the expression is not a naked variable reference, then evaluate the expression. - if (result == null) - { - result = - await _editorSession.DebugService.EvaluateExpressionAsync( - evaluateParams.Expression, - evaluateParams.FrameId, - isFromRepl); - } - } - - if (result != null) - { - valueString = result.ValueString; - variableId = - result.IsExpandable ? - result.Id : 0; - } - } - - await requestContext.SendResultAsync( - new EvaluateResponseBody - { - Result = valueString, - VariablesReference = variableId - }); - } - - private async Task WriteUseIntegratedConsoleMessageAsync() - { - await _messageSender.SendEventAsync( - OutputEvent.Type, - new OutputEventBody - { - Output = "\nThe Debug Console is no longer used for PowerShell debugging. Please use the 'PowerShell Integrated Console' to execute commands in the debugger. Run the 'PowerShell: Show Integrated Console' command to open it.", - Category = "stderr" - }); - } - - private void RegisterEventHandlers() - { - _editorSession.PowerShellContext.RunspaceChanged += powerShellContext_RunspaceChangedAsync; - _editorSession.DebugService.BreakpointUpdated += DebugService_BreakpointUpdatedAsync; - _editorSession.DebugService.DebuggerStopped += DebugService_DebuggerStoppedAsync; - _editorSession.PowerShellContext.DebuggerResumed += powerShellContext_DebuggerResumedAsync; - } - - private void UnregisterEventHandlers() - { - _editorSession.PowerShellContext.RunspaceChanged -= powerShellContext_RunspaceChangedAsync; - _editorSession.DebugService.BreakpointUpdated -= DebugService_BreakpointUpdatedAsync; - _editorSession.DebugService.DebuggerStopped -= DebugService_DebuggerStoppedAsync; - _editorSession.PowerShellContext.DebuggerResumed -= powerShellContext_DebuggerResumedAsync; - } - - private async Task ClearSessionBreakpointsAsync() - { - try - { - await _editorSession.DebugService.ClearAllBreakpointsAsync(); - } - catch (Exception e) - { - Logger.WriteException("Caught exception while clearing breakpoints from session", e); - } - } - - #endregion - - #region Event Handlers - - async void DebugService_DebuggerStoppedAsync(object sender, DebuggerStoppedEventArgs e) - { - // Provide the reason for why the debugger has stopped script execution. - // See https://github.com/Microsoft/vscode/issues/3648 - // The reason is displayed in the breakpoints viewlet. Some recommended reasons are: - // "step", "breakpoint", "function breakpoint", "exception" and "pause". - // We don't support exception breakpoints and for "pause", we can't distinguish - // between stepping and the user pressing the pause/break button in the debug toolbar. - string debuggerStoppedReason = "step"; - if (e.OriginalEvent.Breakpoints.Count > 0) - { - debuggerStoppedReason = - e.OriginalEvent.Breakpoints[0] is CommandBreakpoint - ? "function breakpoint" - : "breakpoint"; - } - - await _messageSender.SendEventAsync( - StoppedEvent.Type, - new StoppedEventBody - { - Source = new Source - { - Path = e.ScriptPath, - }, - ThreadId = 1, - Reason = debuggerStoppedReason - }); - } - - async void powerShellContext_RunspaceChangedAsync(object sender, RunspaceChangedEventArgs e) - { - if (_waitingForAttach && - e.ChangeAction == RunspaceChangeAction.Enter && - e.NewRunspace.Context == RunspaceContext.DebuggedRunspace) - { - // Send the InitializedEvent so that the debugger will continue - // sending configuration requests - _waitingForAttach = false; - await _messageSender.SendEventAsync(InitializedEvent.Type, null); - } - else if ( - e.ChangeAction == RunspaceChangeAction.Exit && - (_editorSession == null || - _editorSession.PowerShellContext.IsDebuggerStopped)) - { - // Exited the session while the debugger is stopped, - // send a ContinuedEvent so that the client changes the - // UI to appear to be running again - await _messageSender.SendEventAsync( - ContinuedEvent.Type, - new ContinuedEvent - { - ThreadId = 1, - AllThreadsContinued = true - }); - } - } - - private async void powerShellContext_DebuggerResumedAsync(object sender, DebuggerResumeAction e) - { - await _messageSender.SendEventAsync( - ContinuedEvent.Type, - new ContinuedEvent - { - AllThreadsContinued = true, - ThreadId = 1 - }); - } - - private async void DebugService_BreakpointUpdatedAsync(object sender, BreakpointUpdatedEventArgs e) - { - string reason = "changed"; - - if (_setBreakpointInProgress) - { - // Don't send breakpoint update notifications when setting - // breakpoints on behalf of the client. - return; - } - - switch (e.UpdateType) - { - case BreakpointUpdateType.Set: - reason = "new"; - break; - - case BreakpointUpdateType.Removed: - reason = "removed"; - break; - } - - Protocol.DebugAdapter.Breakpoint breakpoint; - if (e.Breakpoint is LineBreakpoint) - { - breakpoint = Protocol.DebugAdapter.Breakpoint.Create(BreakpointDetails.Create(e.Breakpoint)); - } - else if (e.Breakpoint is CommandBreakpoint) - { - //breakpoint = Protocol.DebugAdapter.Breakpoint.Create(CommandBreakpointDetails.Create(e.Breakpoint)); - Logger.Write(LogLevel.Verbose, "Function breakpoint updated event is not supported yet"); - return; - } - else - { - Logger.Write(LogLevel.Error, $"Unrecognized breakpoint type {e.Breakpoint.GetType().FullName}"); - return; - } - - breakpoint.Verified = e.UpdateType != BreakpointUpdateType.Disabled; - - await _messageSender.SendEventAsync( - BreakpointEvent.Type, - new BreakpointEvent - { - Reason = reason, - Breakpoint = breakpoint - }); - } - - #endregion - - #region Events - - public event EventHandler SessionEnded; - - protected virtual void OnSessionEnded() - { - SessionEnded?.Invoke(this, null); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices.Protocol/Server/IMessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/Server/IMessageDispatcher.cs deleted file mode 100644 index 156acb9b2..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/IMessageDispatcher.cs +++ /dev/null @@ -1,17 +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.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; - -namespace Microsoft.PowerShell.EditorServices.Protocol -{ - public interface IMessageDispatcher - { - Task DispatchMessageAsync( - Message messageToDispatch, - MessageWriter messageWriter); - } -} diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs deleted file mode 100644 index 06ac6bd7b..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ /dev/null @@ -1,2072 +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 Microsoft.PowerShell.EditorServices.Debugging; -using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Templates; -using Microsoft.PowerShell.EditorServices.Utility; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Management.Automation.Language; -using System.Management.Automation.Runspaces; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -using DebugAdapterMessages = Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - using System.Management.Automation; - - public class LanguageServer - { - private static CancellationTokenSource s_existingRequestCancellation; - - private static readonly Location[] s_emptyLocationResult = new Location[0]; - - private static readonly CompletionItem[] s_emptyCompletionResult = new CompletionItem[0]; - - private static readonly SignatureInformation[] s_emptySignatureResult = new SignatureInformation[0]; - - private static readonly DocumentHighlight[] s_emptyHighlightResult = new DocumentHighlight[0]; - - private static readonly SymbolInformation[] s_emptySymbolResult = new SymbolInformation[0]; - - private ILogger Logger; - private bool profilesLoaded; - private bool consoleReplStarted; - private EditorSession editorSession; - private IMessageSender messageSender; - private IMessageHandlers messageHandlers; - private LanguageServerEditorOperations editorOperations; - private LanguageServerSettings currentSettings = new LanguageServerSettings(); - - // The outer key is the file's uri, the inner key is a unique id for the diagnostic - private Dictionary> codeActionsPerFile = - new Dictionary>(); - - private TaskCompletionSource serverCompletedTask; - - public IEditorOperations EditorOperations - { - get { return this.editorOperations; } - } - - /// - /// Initializes a new language server that is used for handing language server protocol messages - /// - /// The editor session that handles the PowerShell runspace - /// An object that manages all of the message handlers - /// The message sender - /// A TaskCompletionSource that will be completed to stop the running process - /// The logger. - public LanguageServer( - EditorSession editorSession, - IMessageHandlers messageHandlers, - IMessageSender messageSender, - TaskCompletionSource serverCompletedTask, - ILogger logger) - { - this.Logger = logger; - this.editorSession = editorSession; - this.serverCompletedTask = serverCompletedTask; - // Attach to the underlying PowerShell context to listen for changes in the runspace or execution status - this.editorSession.PowerShellContext.RunspaceChanged += PowerShellContext_RunspaceChangedAsync; - this.editorSession.PowerShellContext.ExecutionStatusChanged += PowerShellContext_ExecutionStatusChangedAsync; - - // Attach to ExtensionService events - this.editorSession.ExtensionService.CommandAdded += ExtensionService_ExtensionAddedAsync; - this.editorSession.ExtensionService.CommandUpdated += ExtensionService_ExtensionUpdatedAsync; - this.editorSession.ExtensionService.CommandRemoved += ExtensionService_ExtensionRemovedAsync; - - this.messageSender = messageSender; - this.messageHandlers = messageHandlers; - - // Create the IEditorOperations implementation - this.editorOperations = - new LanguageServerEditorOperations( - this.editorSession, - this.messageSender); - - this.editorSession.StartDebugService(this.editorOperations); - this.editorSession.DebugService.DebuggerStopped += DebugService_DebuggerStoppedAsync; - } - - /// - /// Starts the language server client and sends the Initialize method. - /// - /// A Task that can be awaited for initialization to complete. - public void Start() - { - // Register all supported message types - - this.messageHandlers.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequestAsync); - this.messageHandlers.SetEventHandler(ExitNotification.Type, this.HandleExitNotificationAsync); - - this.messageHandlers.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequestAsync); - this.messageHandlers.SetEventHandler(InitializedNotification.Type, this.HandleInitializedNotificationAsync); - - this.messageHandlers.SetEventHandler(DidOpenTextDocumentNotification.Type, this.HandleDidOpenTextDocumentNotificationAsync); - this.messageHandlers.SetEventHandler(DidCloseTextDocumentNotification.Type, this.HandleDidCloseTextDocumentNotificationAsync); - this.messageHandlers.SetEventHandler(DidSaveTextDocumentNotification.Type, this.HandleDidSaveTextDocumentNotificationAsync); - this.messageHandlers.SetEventHandler(DidChangeTextDocumentNotification.Type, this.HandleDidChangeTextDocumentNotificationAsync); - this.messageHandlers.SetEventHandler(DidChangeConfigurationNotification.Type, this.HandleDidChangeConfigurationNotificationAsync); - - this.messageHandlers.SetRequestHandler(DefinitionRequest.Type, this.HandleDefinitionRequestAsync); - this.messageHandlers.SetRequestHandler(ReferencesRequest.Type, this.HandleReferencesRequestAsync); - this.messageHandlers.SetRequestHandler(CompletionRequest.Type, this.HandleCompletionRequestAsync); - this.messageHandlers.SetRequestHandler(CompletionResolveRequest.Type, this.HandleCompletionResolveRequestAsync); - this.messageHandlers.SetRequestHandler(SignatureHelpRequest.Type, this.HandleSignatureHelpRequestAsync); - this.messageHandlers.SetRequestHandler(DocumentHighlightRequest.Type, this.HandleDocumentHighlightRequestAsync); - this.messageHandlers.SetRequestHandler(HoverRequest.Type, this.HandleHoverRequestAsync); - this.messageHandlers.SetRequestHandler(WorkspaceSymbolRequest.Type, this.HandleWorkspaceSymbolRequestAsync); - this.messageHandlers.SetRequestHandler(CodeActionRequest.Type, this.HandleCodeActionRequestAsync); - this.messageHandlers.SetRequestHandler(DocumentFormattingRequest.Type, this.HandleDocumentFormattingRequestAsync); - this.messageHandlers.SetRequestHandler( - DocumentRangeFormattingRequest.Type, - this.HandleDocumentRangeFormattingRequestAsync); - this.messageHandlers.SetRequestHandler(FoldingRangeRequest.Type, this.HandleFoldingRangeRequestAsync); - - this.messageHandlers.SetRequestHandler(ShowHelpRequest.Type, this.HandleShowHelpRequestAsync); - - this.messageHandlers.SetRequestHandler(ExpandAliasRequest.Type, this.HandleExpandAliasRequestAsync); - this.messageHandlers.SetRequestHandler(GetCommandRequest.Type, this.HandleGetCommandRequestAsync); - - this.messageHandlers.SetRequestHandler(FindModuleRequest.Type, this.HandleFindModuleRequestAsync); - this.messageHandlers.SetRequestHandler(InstallModuleRequest.Type, this.HandleInstallModuleRequestAsync); - - this.messageHandlers.SetRequestHandler(InvokeExtensionCommandRequest.Type, this.HandleInvokeExtensionCommandRequestAsync); - - this.messageHandlers.SetRequestHandler(PowerShellVersionRequest.Type, this.HandlePowerShellVersionRequestAsync); - - this.messageHandlers.SetRequestHandler(NewProjectFromTemplateRequest.Type, this.HandleNewProjectFromTemplateRequestAsync); - this.messageHandlers.SetRequestHandler(GetProjectTemplatesRequest.Type, this.HandleGetProjectTemplatesRequestAsync); - - this.messageHandlers.SetRequestHandler(DebugAdapterMessages.EvaluateRequest.Type, this.HandleEvaluateRequestAsync); - - this.messageHandlers.SetRequestHandler(GetPSSARulesRequest.Type, this.HandleGetPSSARulesRequestAsync); - this.messageHandlers.SetRequestHandler(SetPSSARulesRequest.Type, this.HandleSetPSSARulesRequestAsync); - - this.messageHandlers.SetRequestHandler(ScriptRegionRequest.Type, this.HandleGetFormatScriptRegionRequestAsync); - - this.messageHandlers.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequestAsync); - this.messageHandlers.SetRequestHandler(CommentHelpRequest.Type, this.HandleCommentHelpRequestAsync); - - this.messageHandlers.SetRequestHandler(GetRunspaceRequest.Type, this.HandleGetRunspaceRequestAsync); - - // Initialize the extension service - // TODO: This should be made awaited once Initialize is async! - this.editorSession.ExtensionService.InitializeAsync( - this.editorOperations, - this.editorSession.Components).Wait(); - } - - protected Task Stop() - { - Logger.Write(LogLevel.Normal, "Language service is shutting down..."); - - // complete the task so that the host knows to shut down - this.serverCompletedTask.SetResult(true); - - return Task.FromResult(true); - } - - #region Built-in Message Handlers - - private async Task HandleShutdownRequestAsync( - RequestContext requestContext) - { - // Allow the implementor to shut down gracefully - - await requestContext.SendResultAsync(new object()); - } - - private async Task HandleExitNotificationAsync( - object exitParams, - EventContext eventContext) - { - // Stop the server channel - await this.Stop(); - } - - private Task HandleInitializedNotificationAsync(InitializedParams initializedParams, - EventContext eventContext) - { - // Can do dynamic registration of capabilities in this notification handler - return Task.FromResult(true); - } - - protected async Task HandleInitializeRequestAsync( - InitializeParams initializeParams, - RequestContext requestContext) - { - // Grab the workspace path from the parameters - editorSession.Workspace.WorkspacePath = initializeParams.RootPath; - - // Set the working directory of the PowerShell session to the workspace path - if (editorSession.Workspace.WorkspacePath != null - && Directory.Exists(editorSession.Workspace.WorkspacePath)) - { - await editorSession.PowerShellContext.SetWorkingDirectoryAsync( - editorSession.Workspace.WorkspacePath, - isPathAlreadyEscaped: false); - } - - await requestContext.SendResultAsync( - new InitializeResult - { - Capabilities = new ServerCapabilities - { - TextDocumentSync = TextDocumentSyncKind.Incremental, - DefinitionProvider = true, - ReferencesProvider = true, - DocumentHighlightProvider = true, - DocumentSymbolProvider = true, - WorkspaceSymbolProvider = true, - HoverProvider = true, - CodeActionProvider = true, - CodeLensProvider = new CodeLensOptions { ResolveProvider = true }, - CompletionProvider = new CompletionOptions - { - ResolveProvider = true, - TriggerCharacters = new string[] { ".", "-", ":", "\\" } - }, - SignatureHelpProvider = new SignatureHelpOptions - { - TriggerCharacters = new string[] { " " } // TODO: Other characters here? - }, - DocumentFormattingProvider = false, - DocumentRangeFormattingProvider = false, - RenameProvider = false, - FoldingRangeProvider = true - } - }); - } - - protected async Task HandleShowHelpRequestAsync( - string helpParams, - RequestContext requestContext) - { - const string CheckHelpScript = @" - [CmdletBinding()] - param ( - [String]$CommandName - ) - try { - $command = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop - } catch [System.Management.Automation.CommandNotFoundException] { - $PSCmdlet.ThrowTerminatingError($PSItem) - } - try { - $helpUri = [Microsoft.PowerShell.Commands.GetHelpCodeMethods]::GetHelpUri($command) - - $oldSslVersion = [System.Net.ServicePointManager]::SecurityProtocol - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 - - # HEAD means we don't need the content itself back, just the response header - $status = (Microsoft.PowerShell.Utility\Invoke-WebRequest -Method Head -Uri $helpUri -TimeoutSec 5 -ErrorAction Stop).StatusCode - if ($status -lt 400) { - $null = Microsoft.PowerShell.Core\Get-Help $CommandName -Online - return - } - } catch { - # Ignore - we want to drop out to Get-Help -Full - } finally { - [System.Net.ServicePointManager]::SecurityProtocol = $oldSslVersion - } - - return Microsoft.PowerShell.Core\Get-Help $CommandName -Full - "; - - if (string.IsNullOrEmpty(helpParams)) { helpParams = "Get-Help"; } - - PSCommand checkHelpPSCommand = new PSCommand() - .AddScript(CheckHelpScript, useLocalScope: true) - .AddArgument(helpParams); - - // TODO: Rather than print the help in the console, we should send the string back - // to VSCode to display in a help pop-up (or similar) - await editorSession.PowerShellContext.ExecuteCommandAsync(checkHelpPSCommand, sendOutputToHost: true); - await requestContext.SendResultAsync(null); - } - - private async Task HandleSetPSSARulesRequestAsync( - object param, - RequestContext requestContext) - { - var dynParams = param as dynamic; - if (editorSession.AnalysisService != null && - editorSession.AnalysisService.SettingsPath == null) - { - var activeRules = new List(); - var ruleInfos = dynParams.ruleInfos; - foreach (dynamic ruleInfo in ruleInfos) - { - if ((Boolean)ruleInfo.isEnabled) - { - activeRules.Add((string)ruleInfo.name); - } - } - editorSession.AnalysisService.ActiveRules = activeRules.ToArray(); - } - - var sendresult = requestContext.SendResultAsync(null); - var scripFile = editorSession.Workspace.GetFile((string)dynParams.filepath); - await RunScriptDiagnosticsAsync( - new ScriptFile[] { scripFile }, - editorSession, - this.messageSender.SendEventAsync); - await sendresult; - } - - private async Task HandleGetFormatScriptRegionRequestAsync( - ScriptRegionRequestParams requestParams, - RequestContext requestContext) - { - var scriptFile = this.editorSession.Workspace.GetFile(requestParams.FileUri); - var lineNumber = requestParams.Line; - var columnNumber = requestParams.Column; - ScriptRegion scriptRegion = null; - - switch (requestParams.Character) - { - case "\n": - // find the smallest statement ast that occupies - // the element before \n or \r\n and return the extent. - --lineNumber; // vscode sends the next line when pressed enter - var line = scriptFile.GetLine(lineNumber); - if (!String.IsNullOrEmpty(line)) - { - scriptRegion = this.editorSession.LanguageService.FindSmallestStatementAstRegion( - scriptFile, - lineNumber, - line.Length); - } - break; - - case "}": - scriptRegion = this.editorSession.LanguageService.FindSmallestStatementAstRegion( - scriptFile, - lineNumber, - columnNumber); - break; - - default: - break; - } - - await requestContext.SendResultAsync(new ScriptRegionRequestResult - { - scriptRegion = scriptRegion - }); - } - - private async Task HandleGetPSSARulesRequestAsync( - object param, - RequestContext requestContext) - { - List rules = null; - if (editorSession.AnalysisService != null - && editorSession.AnalysisService.SettingsPath == null) - { - rules = new List(); - var ruleNames = editorSession.AnalysisService.GetPSScriptAnalyzerRules(); - var activeRules = editorSession.AnalysisService.ActiveRules; - foreach (var ruleName in ruleNames) - { - rules.Add(new { name = ruleName, isEnabled = activeRules.Contains(ruleName, StringComparer.OrdinalIgnoreCase) }); - } - } - - await requestContext.SendResultAsync(rules); - } - - private async Task HandleInstallModuleRequestAsync( - string moduleName, - RequestContext requestContext - ) - { - var script = string.Format("Install-Module -Name {0} -Scope CurrentUser", moduleName); - - var executeTask = - editorSession.PowerShellContext.ExecuteScriptStringAsync( - script, - true, - true).ConfigureAwait(false); - - await requestContext.SendResultAsync(null); - } - - private Task HandleInvokeExtensionCommandRequestAsync( - InvokeExtensionCommandRequest commandDetails, - RequestContext requestContext) - { - // We don't await the result of the execution here because we want - // to be able to receive further messages while the editor command - // is executing. This important in cases where the pipeline thread - // gets blocked by something in the script like a prompt to the user. - EditorContext editorContext = - this.editorOperations.ConvertClientEditorContext( - commandDetails.Context); - - Task commandTask = - this.editorSession.ExtensionService.InvokeCommandAsync( - commandDetails.Name, - editorContext); - - commandTask.ContinueWith(t => - { - return requestContext.SendResultAsync(null); - }); - - return Task.FromResult(true); - } - - private Task HandleNewProjectFromTemplateRequestAsync( - NewProjectFromTemplateRequest newProjectArgs, - RequestContext requestContext) - { - // Don't await the Task here so that we don't block the session - this.editorSession.TemplateService - .CreateFromTemplateAsync(newProjectArgs.TemplatePath, newProjectArgs.DestinationPath) - .ContinueWith( - async task => - { - await requestContext.SendResultAsync( - new NewProjectFromTemplateResponse - { - CreationSuccessful = task.Result - }); - }); - - return Task.FromResult(true); - } - - private async Task HandleGetProjectTemplatesRequestAsync( - GetProjectTemplatesRequest requestArgs, - RequestContext requestContext) - { - bool plasterInstalled = await this.editorSession.TemplateService.ImportPlasterIfInstalledAsync(); - - if (plasterInstalled) - { - var availableTemplates = - await this.editorSession.TemplateService.GetAvailableTemplatesAsync( - requestArgs.IncludeInstalledModules); - - await requestContext.SendResultAsync( - new GetProjectTemplatesResponse - { - Templates = availableTemplates - }); - } - else - { - await requestContext.SendResultAsync( - new GetProjectTemplatesResponse - { - NeedsModuleInstall = true, - Templates = new TemplateDetails[0] - }); - } - } - - private async Task HandleExpandAliasRequestAsync( - string content, - RequestContext requestContext) - { - var script = @" -function __Expand-Alias { - - param($targetScript) - - [ref]$errors=$null - - $tokens = [System.Management.Automation.PsParser]::Tokenize($targetScript, $errors).Where({$_.type -eq 'command'}) | - Sort-Object Start -Descending - - foreach ($token in $tokens) { - $definition=(Get-Command ('`'+$token.Content) -CommandType Alias -ErrorAction SilentlyContinue).Definition - - if($definition) { - $lhs=$targetScript.Substring(0, $token.Start) - $rhs=$targetScript.Substring($token.Start + $token.Length) - - $targetScript=$lhs + $definition + $rhs - } - } - - $targetScript -}"; - var psCommand = new PSCommand(); - psCommand.AddScript(script); - await this.editorSession.PowerShellContext.ExecuteCommandAsync(psCommand); - - psCommand = new PSCommand(); - psCommand.AddCommand("__Expand-Alias").AddArgument(content); - var result = await this.editorSession.PowerShellContext.ExecuteCommandAsync(psCommand); - - await requestContext.SendResultAsync(result.First().ToString()); - } - - private async Task HandleGetCommandRequestAsync( - string param, - RequestContext requestContext) - { - PSCommand psCommand = new PSCommand(); - if (!string.IsNullOrEmpty(param)) - { - psCommand.AddCommand("Microsoft.PowerShell.Core\\Get-Command").AddArgument(param); - } - else - { - // Executes the following: - // Get-Command -CommandType Function,Cmdlet,ExternalScript | Select-Object -Property Name,ModuleName | Sort-Object -Property Name - psCommand - .AddCommand("Microsoft.PowerShell.Core\\Get-Command") - .AddParameter("CommandType", new[]{"Function", "Cmdlet", "ExternalScript"}) - .AddCommand("Microsoft.PowerShell.Utility\\Select-Object") - .AddParameter("Property", new[]{"Name", "ModuleName"}) - .AddCommand("Microsoft.PowerShell.Utility\\Sort-Object") - .AddParameter("Property", "Name"); - } - - IEnumerable result = await this.editorSession.PowerShellContext.ExecuteCommandAsync(psCommand); - - var commandList = new List(); - if (result != null) - { - foreach (dynamic command in result) - { - commandList.Add(new PSCommandMessage - { - Name = command.Name, - ModuleName = command.ModuleName, - Parameters = command.Parameters, - ParameterSets = command.ParameterSets, - DefaultParameterSet = command.DefaultParameterSet - }); - } - } - - await requestContext.SendResultAsync(commandList); - } - - private async Task HandleFindModuleRequestAsync( - object param, - RequestContext requestContext) - { - var psCommand = new PSCommand(); - psCommand.AddScript("Find-Module | Select Name, Description"); - - var modules = await editorSession.PowerShellContext.ExecuteCommandAsync(psCommand); - - var moduleList = new List(); - - if (modules != null) - { - foreach (dynamic m in modules) - { - moduleList.Add(new PSModuleMessage { Name = m.Name, Description = m.Description }); - } - } - - await requestContext.SendResultAsync(moduleList); - } - - protected Task HandleDidOpenTextDocumentNotificationAsync( - DidOpenTextDocumentParams openParams, - EventContext eventContext) - { - ScriptFile openedFile = - editorSession.Workspace.GetFileBuffer( - openParams.TextDocument.Uri, - openParams.TextDocument.Text); - - // TODO: Get all recently edited files in the workspace - this.RunScriptDiagnosticsAsync( - new ScriptFile[] { openedFile }, - editorSession, - eventContext); - - Logger.Write(LogLevel.Verbose, "Finished opening document."); - - return Task.FromResult(true); - } - - protected async Task HandleDidCloseTextDocumentNotificationAsync( - DidCloseTextDocumentParams closeParams, - EventContext eventContext) - { - // Find and close the file in the current session - var fileToClose = editorSession.Workspace.GetFile(closeParams.TextDocument.Uri); - - if (fileToClose != null) - { - editorSession.Workspace.CloseFile(fileToClose); - await ClearMarkersAsync(fileToClose, eventContext); - } - - Logger.Write(LogLevel.Verbose, "Finished closing document."); - } - protected async Task HandleDidSaveTextDocumentNotificationAsync( - 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.SaveRemoteFileAsync( - savedFile.FilePath); - } - } - } - - protected Task HandleDidChangeTextDocumentNotificationAsync( - DidChangeTextDocumentParams textChangeParams, - EventContext eventContext) - { - List changedFiles = new List(); - - // A text change notification can batch multiple change requests - foreach (var textChange in textChangeParams.ContentChanges) - { - ScriptFile changedFile = editorSession.Workspace.GetFile(textChangeParams.TextDocument.Uri); - - changedFile.ApplyChange( - GetFileChangeDetails( - textChange.Range, - textChange.Text)); - - changedFiles.Add(changedFile); - } - - // TODO: Get all recently edited files in the workspace - this.RunScriptDiagnosticsAsync( - changedFiles.ToArray(), - editorSession, - eventContext); - - return Task.FromResult(true); - } - - protected async Task HandleDidChangeConfigurationNotificationAsync( - DidChangeConfigurationParams configChangeParams, - EventContext eventContext) - { - bool oldLoadProfiles = this.currentSettings.EnableProfileLoading; - bool oldScriptAnalysisEnabled = - this.currentSettings.ScriptAnalysis.Enable.HasValue ? this.currentSettings.ScriptAnalysis.Enable.Value : false; - string oldScriptAnalysisSettingsPath = - this.currentSettings.ScriptAnalysis?.SettingsPath; - - this.currentSettings.Update( - configChangeParams.Settings.Powershell, - this.editorSession.Workspace.WorkspacePath, - this.Logger); - - if (!this.profilesLoaded && - this.currentSettings.EnableProfileLoading && - oldLoadProfiles != this.currentSettings.EnableProfileLoading) - { - await this.editorSession.PowerShellContext.LoadHostProfilesAsync(); - this.profilesLoaded = true; - } - - // Wait until after profiles are loaded (or not, if that's the - // case) before starting the interactive console. - if (!this.consoleReplStarted) - { - // Start the interactive terminal - this.editorSession.HostInput.StartCommandLoop(); - this.consoleReplStarted = true; - } - - // If there is a new settings file path, restart the analyzer with the new settigs. - bool settingsPathChanged = false; - string newSettingsPath = this.currentSettings.ScriptAnalysis.SettingsPath; - if (!string.Equals(oldScriptAnalysisSettingsPath, newSettingsPath, StringComparison.OrdinalIgnoreCase)) - { - if (this.editorSession.AnalysisService != null) - { - this.editorSession.AnalysisService.SettingsPath = newSettingsPath; - settingsPathChanged = true; - } - } - - // If script analysis settings have changed we need to clear & possibly update the current diagnostic records. - if ((oldScriptAnalysisEnabled != this.currentSettings.ScriptAnalysis?.Enable) || settingsPathChanged) - { - // If the user just turned off script analysis or changed the settings path, send a diagnostics - // event to clear the analysis markers that they already have. - if (!this.currentSettings.ScriptAnalysis.Enable.Value || settingsPathChanged) - { - foreach (var scriptFile in editorSession.Workspace.GetOpenedFiles()) - { - await ClearMarkersAsync(scriptFile, eventContext); - } - } - - await this.RunScriptDiagnosticsAsync( - this.editorSession.Workspace.GetOpenedFiles(), - this.editorSession, - eventContext); - } - - // Convert the editor file glob patterns into an array for the Workspace - // Both the files.exclude and search.exclude hash tables look like (glob-text, is-enabled): - // "files.exclude" : { - // "Makefile": true, - // "*.html": true, - // "build/*": true - // } - var excludeFilePatterns = new List(); - if (configChangeParams.Settings.Files?.Exclude != null) - { - foreach(KeyValuePair patternEntry in configChangeParams.Settings.Files.Exclude) - { - if (patternEntry.Value) { excludeFilePatterns.Add(patternEntry.Key); } - } - } - if (configChangeParams.Settings.Search?.Exclude != null) - { - foreach(KeyValuePair patternEntry in configChangeParams.Settings.Files.Exclude) - { - if (patternEntry.Value && !excludeFilePatterns.Contains(patternEntry.Key)) { excludeFilePatterns.Add(patternEntry.Key); } - } - } - editorSession.Workspace.ExcludeFilesGlob = excludeFilePatterns; - - // Convert the editor file search options to Workspace properties - if (configChangeParams.Settings.Search?.FollowSymlinks != null) - { - editorSession.Workspace.FollowSymlinks = configChangeParams.Settings.Search.FollowSymlinks; - } - } - - protected async Task HandleDefinitionRequestAsync( - TextDocumentPositionParams textDocumentPosition, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - textDocumentPosition.TextDocument.Uri); - - SymbolReference foundSymbol = - editorSession.LanguageService.FindSymbolAtLocation( - scriptFile, - textDocumentPosition.Position.Line + 1, - textDocumentPosition.Position.Character + 1); - - List definitionLocations = new List(); - - GetDefinitionResult definition = null; - if (foundSymbol != null) - { - definition = - await editorSession.LanguageService.GetDefinitionOfSymbolAsync( - scriptFile, - foundSymbol, - editorSession.Workspace); - - if (definition != null) - { - definitionLocations.Add( - new Location - { - Uri = GetFileUri(definition.FoundDefinition.FilePath), - Range = GetRangeFromScriptRegion(definition.FoundDefinition.ScriptRegion) - }); - } - } - - await requestContext.SendResultAsync(definitionLocations.ToArray()); - } - - protected async Task HandleReferencesRequestAsync( - ReferencesParams referencesParams, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - referencesParams.TextDocument.Uri); - - SymbolReference foundSymbol = - editorSession.LanguageService.FindSymbolAtLocation( - scriptFile, - referencesParams.Position.Line + 1, - referencesParams.Position.Character + 1); - - FindReferencesResult referencesResult = - await editorSession.LanguageService.FindReferencesOfSymbolAsync( - foundSymbol, - editorSession.Workspace.ExpandScriptReferences(scriptFile), - editorSession.Workspace); - - Location[] referenceLocations = s_emptyLocationResult; - - if (referencesResult != null) - { - var locations = new List(); - foreach (SymbolReference foundReference in referencesResult.FoundReferences) - { - locations.Add(new Location - { - Uri = GetFileUri(foundReference.FilePath), - Range = GetRangeFromScriptRegion(foundReference.ScriptRegion) - }); - } - referenceLocations = locations.ToArray(); - } - - await requestContext.SendResultAsync(referenceLocations); - } - - protected async Task HandleCompletionRequestAsync( - TextDocumentPositionParams textDocumentPositionParams, - RequestContext requestContext) - { - int cursorLine = textDocumentPositionParams.Position.Line + 1; - int cursorColumn = textDocumentPositionParams.Position.Character + 1; - - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - textDocumentPositionParams.TextDocument.Uri); - - CompletionResults completionResults = - await editorSession.LanguageService.GetCompletionsInFileAsync( - scriptFile, - cursorLine, - cursorColumn); - - CompletionItem[] completionItems = s_emptyCompletionResult; - - if (completionResults != null) - { - completionItems = new CompletionItem[completionResults.Completions.Length]; - for (int i = 0; i < completionItems.Length; i++) - { - completionItems[i] = CreateCompletionItem(completionResults.Completions[i], completionResults.ReplacedRange, i + 1); - } - } - - await requestContext.SendResultAsync(completionItems); - } - - protected async Task HandleCompletionResolveRequestAsync( - CompletionItem completionItem, - RequestContext requestContext) - { - if (completionItem.Kind == CompletionItemKind.Function) - { - // Get the documentation for the function - CommandInfo commandInfo = - await CommandHelpers.GetCommandInfoAsync( - completionItem.Label, - this.editorSession.PowerShellContext); - - if (commandInfo != null) - { - completionItem.Documentation = - await CommandHelpers.GetCommandSynopsisAsync( - commandInfo, - this.editorSession.PowerShellContext); - } - } - - // Send back the updated CompletionItem - await requestContext.SendResultAsync(completionItem); - } - - protected async Task HandleSignatureHelpRequestAsync( - TextDocumentPositionParams textDocumentPositionParams, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - textDocumentPositionParams.TextDocument.Uri); - - ParameterSetSignatures parameterSets = - await editorSession.LanguageService.FindParameterSetsInFileAsync( - scriptFile, - textDocumentPositionParams.Position.Line + 1, - textDocumentPositionParams.Position.Character + 1); - - SignatureInformation[] signatures = s_emptySignatureResult; - - if (parameterSets != null) - { - signatures = new SignatureInformation[parameterSets.Signatures.Length]; - for (int i = 0; i < signatures.Length; i++) - { - var parameters = new ParameterInformation[parameterSets.Signatures[i].Parameters.Count()]; - int j = 0; - foreach (ParameterInfo param in parameterSets.Signatures[i].Parameters) - { - parameters[j] = CreateParameterInfo(param); - j++; - } - - signatures[i] = new SignatureInformation - { - Label = parameterSets.CommandName + " " + parameterSets.Signatures[i].SignatureText, - Documentation = null, - Parameters = parameters, - }; - } - } - - await requestContext.SendResultAsync( - new SignatureHelp - { - Signatures = signatures, - ActiveParameter = null, - ActiveSignature = 0 - }); - } - - protected async Task HandleDocumentHighlightRequestAsync( - TextDocumentPositionParams textDocumentPositionParams, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - textDocumentPositionParams.TextDocument.Uri); - - FindOccurrencesResult occurrencesResult = - editorSession.LanguageService.FindOccurrencesInFile( - scriptFile, - textDocumentPositionParams.Position.Line + 1, - textDocumentPositionParams.Position.Character + 1); - - DocumentHighlight[] documentHighlights = s_emptyHighlightResult; - - if (occurrencesResult != null) - { - var highlights = new List(); - foreach (SymbolReference foundOccurrence in occurrencesResult.FoundOccurrences) - { - highlights.Add(new DocumentHighlight - { - Kind = DocumentHighlightKind.Write, // TODO: Which symbol types are writable? - Range = GetRangeFromScriptRegion(foundOccurrence.ScriptRegion) - }); - } - documentHighlights = highlights.ToArray(); - } - - await requestContext.SendResultAsync(documentHighlights); - } - - protected async Task HandleHoverRequestAsync( - TextDocumentPositionParams textDocumentPositionParams, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - textDocumentPositionParams.TextDocument.Uri); - - SymbolDetails symbolDetails = - await editorSession - .LanguageService - .FindSymbolDetailsAtLocationAsync( - scriptFile, - textDocumentPositionParams.Position.Line + 1, - textDocumentPositionParams.Position.Character + 1); - - List symbolInfo = new List(); - Range symbolRange = null; - - if (symbolDetails != null) - { - symbolInfo.Add( - new MarkedString - { - Language = "PowerShell", - Value = symbolDetails.DisplayString - }); - - if (!string.IsNullOrEmpty(symbolDetails.Documentation)) - { - symbolInfo.Add( - new MarkedString - { - Language = "markdown", - Value = symbolDetails.Documentation - }); - } - - symbolRange = GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion); - } - - await requestContext.SendResultAsync( - new Hover - { - Contents = symbolInfo.ToArray(), - Range = symbolRange - }); - } - - protected async Task HandleDocumentSymbolRequestAsync( - DocumentSymbolParams documentSymbolParams, - RequestContext requestContext) - { - ScriptFile scriptFile = - editorSession.Workspace.GetFile( - documentSymbolParams.TextDocument.Uri); - - FindOccurrencesResult foundSymbols = - editorSession.LanguageService.FindSymbolsInFile( - scriptFile); - - string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); - - SymbolInformation[] symbols = s_emptySymbolResult; - if (foundSymbols != null) - { - var symbolAcc = new List(); - foreach (SymbolReference foundOccurrence in foundSymbols.FoundOccurrences) - { - var location = new Location - { - Uri = GetFileUri(foundOccurrence.FilePath), - Range = GetRangeFromScriptRegion(foundOccurrence.ScriptRegion) - }; - - symbolAcc.Add(new SymbolInformation - { - ContainerName = containerName, - Kind = GetSymbolKind(foundOccurrence.SymbolType), - Location = location, - Name = GetDecoratedSymbolName(foundOccurrence) - }); - } - symbols = symbolAcc.ToArray(); - } - - await requestContext.SendResultAsync(symbols); - } - - public static SymbolKind GetSymbolKind(SymbolType symbolType) - { - switch (symbolType) - { - case SymbolType.Configuration: - case SymbolType.Function: - case SymbolType.Workflow: - return SymbolKind.Function; - - default: - return SymbolKind.Variable; - } - } - - public static string GetDecoratedSymbolName(SymbolReference symbolReference) - { - string name = symbolReference.SymbolName; - - if (symbolReference.SymbolType == SymbolType.Configuration || - symbolReference.SymbolType == SymbolType.Function || - symbolReference.SymbolType == SymbolType.Workflow) - { - name += " { }"; - } - - return name; - } - - protected async Task HandleWorkspaceSymbolRequestAsync( - WorkspaceSymbolParams workspaceSymbolParams, - RequestContext requestContext) - { - var symbols = new List(); - - foreach (ScriptFile scriptFile in editorSession.Workspace.GetOpenedFiles()) - { - FindOccurrencesResult foundSymbols = - editorSession.LanguageService.FindSymbolsInFile( - scriptFile); - - // TODO: Need to compute a relative path that is based on common path for all workspace files - string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); - - if (foundSymbols != null) - { - foreach (SymbolReference foundOccurrence in foundSymbols.FoundOccurrences) - { - if (!IsQueryMatch(workspaceSymbolParams.Query, foundOccurrence.SymbolName)) - { - continue; - } - - var location = new Location - { - Uri = GetFileUri(foundOccurrence.FilePath), - Range = GetRangeFromScriptRegion(foundOccurrence.ScriptRegion) - }; - - symbols.Add(new SymbolInformation - { - ContainerName = containerName, - Kind = foundOccurrence.SymbolType == SymbolType.Variable ? SymbolKind.Variable : SymbolKind.Function, - Location = location, - Name = GetDecoratedSymbolName(foundOccurrence) - }); - } - } - } - - await requestContext.SendResultAsync(symbols.ToArray()); - } - - protected async Task HandlePowerShellVersionRequestAsync( - object noParams, - RequestContext requestContext) - { - await requestContext.SendResultAsync( - new PowerShellVersion( - this.editorSession.PowerShellContext.LocalPowerShellVersion)); - } - - protected async Task HandleGetPSHostProcessesRequestAsync( - object noParams, - RequestContext requestContext) - { - var psHostProcesses = new List(); - - if (this.editorSession.PowerShellContext.LocalPowerShellVersion.Version.Major >= 5) - { - int processId = System.Diagnostics.Process.GetCurrentProcess().Id; - var psCommand = new PSCommand(); - psCommand.AddCommand("Get-PSHostProcessInfo"); - psCommand.AddCommand("Where-Object") - .AddParameter("Property", "ProcessId") - .AddParameter("NE") - .AddParameter("Value", processId.ToString()); - - var processes = await editorSession.PowerShellContext.ExecuteCommandAsync(psCommand); - if (processes != null) - { - foreach (dynamic p in processes) - { - psHostProcesses.Add( - new GetPSHostProcessesResponse - { - ProcessName = p.ProcessName, - ProcessId = p.ProcessId, - AppDomainName = p.AppDomainName, - MainWindowTitle = p.MainWindowTitle - }); - } - } - } - - await requestContext.SendResultAsync(psHostProcesses.ToArray()); - } - - protected async Task HandleCommentHelpRequestAsync( - CommentHelpRequestParams requestParams, - RequestContext requestContext) - { - var result = new CommentHelpRequestResult(); - - ScriptFile scriptFile; - if (!this.editorSession.Workspace.TryGetFile(requestParams.DocumentUri, out scriptFile)) - { - await requestContext.SendResultAsync(result); - return; - } - - int triggerLine = requestParams.TriggerPosition.Line + 1; - - string helpLocation; - FunctionDefinitionAst functionDefinitionAst = editorSession.LanguageService.GetFunctionDefinitionForHelpComment( - scriptFile, - triggerLine, - out helpLocation); - - if (functionDefinitionAst == null) - { - await requestContext.SendResultAsync(result); - return; - } - - IScriptExtent funcExtent = functionDefinitionAst.Extent; - string funcText = funcExtent.Text; - if (helpLocation.Equals("begin")) - { - // check if the previous character is `<` because it invalidates - // the param block the follows it. - IList lines = ScriptFile.GetLines(funcText); - int relativeTriggerLine0b = triggerLine - funcExtent.StartLineNumber; - if (relativeTriggerLine0b > 0 && lines[relativeTriggerLine0b].IndexOf("<") > -1) - { - lines[relativeTriggerLine0b] = string.Empty; - } - - funcText = string.Join("\n", lines); - } - - List analysisResults = await this.editorSession.AnalysisService.GetSemanticMarkersAsync( - funcText, - AnalysisService.GetCommentHelpRuleSettings( - enable: true, - exportedOnly: false, - blockComment: requestParams.BlockComment, - vscodeSnippetCorrection: true, - placement: helpLocation)); - - string helpText = analysisResults?.FirstOrDefault()?.Correction?.Edits[0].Text; - - if (helpText == null) - { - await requestContext.SendResultAsync(result); - return; - } - - result.Content = ScriptFile.GetLines(helpText).ToArray(); - - if (helpLocation != null && - !helpLocation.Equals("before", StringComparison.OrdinalIgnoreCase)) - { - // we need to trim the leading `{` and newline when helpLocation=="begin" - // we also need to trim the leading newline when helpLocation=="end" - result.Content = result.Content.Skip(1).ToArray(); - } - - await requestContext.SendResultAsync(result); - } - - protected async Task HandleGetRunspaceRequestAsync( - string processId, - RequestContext requestContext) - { - IEnumerable runspaces = null; - - if (this.editorSession.PowerShellContext.LocalPowerShellVersion.Version.Major >= 5) - { - if (processId == null) { - processId = "current"; - } - - // If the processId is a valid int, we need to run Get-Runspace within that process - // otherwise just use the current runspace. - if (int.TryParse(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()) - { - rs.Open(); - ps.Runspace = rs; - // Returns deserialized Runspaces. For simpler code, we use PSObject and rely on dynamic later. - runspaces = ps.AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace").Invoke(); - } - } - else - { - 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 runspaceResponses = new List(); - - if (runspaces != null) - { - foreach (dynamic runspace in runspaces) - { - runspaceResponses.Add( - new GetRunspaceResponse - { - Id = runspace.Id, - Name = runspace.Name, - Availability = runspace.RunspaceAvailability.ToString() - }); - } - } - - await requestContext.SendResultAsync(runspaceResponses.ToArray()); - } - - private bool IsQueryMatch(string query, string symbolName) - { - return symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; - } - - // https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction - protected async Task HandleCodeActionRequestAsync( - CodeActionParams codeActionParams, - RequestContext requestContext) - { - MarkerCorrection correction = null; - Dictionary markerIndex = null; - List codeActionCommands = new List(); - - // If there are any code fixes, send these commands first so they appear at top of "Code Fix" menu in the client UI. - if (this.codeActionsPerFile.TryGetValue(codeActionParams.TextDocument.Uri, out markerIndex)) - { - foreach (var diagnostic in codeActionParams.Context.Diagnostics) - { - if (string.IsNullOrEmpty(diagnostic.Code)) - { - this.Logger.Write( - LogLevel.Warning, - $"textDocument/codeAction skipping diagnostic with empty Code field: {diagnostic.Source} {diagnostic.Message}"); - - continue; - } - - string diagnosticId = GetUniqueIdFromDiagnostic(diagnostic); - if (markerIndex.TryGetValue(diagnosticId, out correction)) - { - codeActionCommands.Add( - new CodeActionCommand - { - Title = correction.Name, - Command = "PowerShell.ApplyCodeActionEdits", - Arguments = JArray.FromObject(correction.Edits) - }); - } - } - } - - // Add "show documentation" commands last so they appear at the bottom of the client UI. - // These commands do not require code fixes. Sometimes we get a batch of diagnostics - // to create commands for. No need to create multiple show doc commands for the same rule. - var ruleNamesProcessed = new HashSet(); - foreach (var diagnostic in codeActionParams.Context.Diagnostics) - { - if (string.IsNullOrEmpty(diagnostic.Code)) { continue; } - - if (string.Equals(diagnostic.Source, "PSScriptAnalyzer", StringComparison.OrdinalIgnoreCase) && - !ruleNamesProcessed.Contains(diagnostic.Code)) - { - ruleNamesProcessed.Add(diagnostic.Code); - - codeActionCommands.Add( - new CodeActionCommand - { - Title = $"Show documentation for \"{diagnostic.Code}\"", - Command = "PowerShell.ShowCodeActionDocumentation", - Arguments = JArray.FromObject(new[] { diagnostic.Code }) - }); - } - } - - await requestContext.SendResultAsync( - codeActionCommands.ToArray()); - } - - protected async Task HandleDocumentFormattingRequestAsync( - DocumentFormattingParams formattingParams, - RequestContext requestContext) - { - var result = await FormatAsync( - formattingParams.TextDocument.Uri, - formattingParams.options, - null); - - await requestContext.SendResultAsync(new TextEdit[1] - { - new TextEdit - { - NewText = result.Item1, - Range = result.Item2 - }, - }); - } - - protected async Task HandleDocumentRangeFormattingRequestAsync( - DocumentRangeFormattingParams formattingParams, - RequestContext requestContext) - { - var result = await FormatAsync( - formattingParams.TextDocument.Uri, - formattingParams.Options, - formattingParams.Range); - - await requestContext.SendResultAsync(new TextEdit[1] - { - new TextEdit - { - NewText = result.Item1, - Range = result.Item2 - }, - }); - } - - protected async Task HandleFoldingRangeRequestAsync( - FoldingRangeParams foldingParams, - RequestContext requestContext) - { - await requestContext.SendResultAsync(Fold(foldingParams.TextDocument.Uri)); - } - - protected Task HandleEvaluateRequestAsync( - DebugAdapterMessages.EvaluateRequestArguments evaluateParams, - RequestContext requestContext) - { - // We don't await the result of the execution here because we want - // to be able to receive further messages while the current script - // is executing. This important in cases where the pipeline thread - // gets blocked by something in the script like a prompt to the user. - var executeTask = - this.editorSession.PowerShellContext.ExecuteScriptStringAsync( - evaluateParams.Expression, - writeInputToHost: true, - writeOutputToHost: true, - addToHistory: true); - - // Return the execution result after the task completes so that the - // caller knows when command execution completed. - executeTask.ContinueWith( - (task) => - { - // Return an empty result since the result value is irrelevant - // for this request in the LanguageServer - return - requestContext.SendResultAsync( - new DebugAdapterMessages.EvaluateResponseBody - { - Result = "", - VariablesReference = 0 - }); - }); - - return Task.FromResult(true); - } - - #endregion - - #region Event Handlers - - private FoldingRange[] Fold(string documentUri) - { - // TODO Should be using dynamic registrations - if (!this.currentSettings.CodeFolding.Enable) { return null; } - - // Avoid crash when using untitled: scheme or any other scheme where the document doesn't - // have a backing file. https://github.com/PowerShell/vscode-powershell/issues/1676 - // Perhaps a better option would be to parse the contents of the document as a string - // as opposed to reading a file but the senario of "no backing file" probably doesn't - // warrant the extra effort. - ScriptFile scriptFile; - if (!editorSession.Workspace.TryGetFile(documentUri, out scriptFile)) { return null; } - - var result = new List(); - - // If we're showing the last line, decrement the Endline of all regions by one. - int endLineOffset = this.currentSettings.CodeFolding.ShowLastLine ? -1 : 0; - - foreach (FoldingReference fold in TokenOperations.FoldableReferences(scriptFile.ScriptTokens).References) - { - result.Add(new FoldingRange { - EndCharacter = fold.EndCharacter, - EndLine = fold.EndLine + endLineOffset, - Kind = fold.Kind, - StartCharacter = fold.StartCharacter, - StartLine = fold.StartLine - }); - } - - return result.ToArray(); - } - - private async Task> FormatAsync( - string documentUri, - FormattingOptions options, - Range range) - { - var scriptFile = editorSession.Workspace.GetFile(documentUri); - var pssaSettings = currentSettings.CodeFormatting.GetPSSASettingsHashtable( - options.TabSize, - options.InsertSpaces); - - // TODO raise an error event in case format returns null; - string formattedScript; - Range editRange; - var rangeList = range == null ? null : new int[] { - range.Start.Line + 1, - range.Start.Character + 1, - range.End.Line + 1, - range.End.Character + 1}; - var extent = scriptFile.ScriptAst.Extent; - - // todo create an extension for converting range to script extent - editRange = new Range - { - Start = new Position - { - Line = extent.StartLineNumber - 1, - Character = extent.StartColumnNumber - 1 - }, - End = new Position - { - Line = extent.EndLineNumber - 1, - Character = extent.EndColumnNumber - 1 - } - }; - - formattedScript = await editorSession.AnalysisService.FormatAsync( - scriptFile.Contents, - pssaSettings, - rangeList); - formattedScript = formattedScript ?? scriptFile.Contents; - return Tuple.Create(formattedScript, editRange); - } - - private async void PowerShellContext_RunspaceChangedAsync(object sender, Session.RunspaceChangedEventArgs e) - { - await this.messageSender.SendEventAsync( - RunspaceChangedEvent.Type, - new Protocol.LanguageServer.RunspaceDetails(e.NewRunspace)); - } - - /// - /// Event hook on the PowerShell context to listen for changes in script execution status - /// - /// the PowerShell context sending the execution event - /// details of the execution status change - private async void PowerShellContext_ExecutionStatusChangedAsync(object sender, ExecutionStatusChangedEventArgs e) - { - await this.messageSender.SendEventAsync( - ExecutionStatusChangedEvent.Type, - e); - } - - private async void ExtensionService_ExtensionAddedAsync(object sender, EditorCommand e) - { - await this.messageSender.SendEventAsync( - ExtensionCommandAddedNotification.Type, - new ExtensionCommandAddedNotification - { - Name = e.Name, - DisplayName = e.DisplayName - }); - } - - private async void ExtensionService_ExtensionUpdatedAsync(object sender, EditorCommand e) - { - await this.messageSender.SendEventAsync( - ExtensionCommandUpdatedNotification.Type, - new ExtensionCommandUpdatedNotification - { - Name = e.Name, - }); - } - - private async void ExtensionService_ExtensionRemovedAsync(object sender, EditorCommand e) - { - await this.messageSender.SendEventAsync( - ExtensionCommandRemovedNotification.Type, - new ExtensionCommandRemovedNotification - { - Name = e.Name, - }); - } - - private async void DebugService_DebuggerStoppedAsync(object sender, DebuggerStoppedEventArgs e) - { - if (!this.editorSession.DebugService.IsClientAttached) - { - await this.messageSender.SendEventAsync( - StartDebuggerEvent.Type, - new StartDebuggerEvent()); - } - } - - #endregion - - #region Helper Methods - - public static string GetFileUri(string filePath) - { - // If the file isn't untitled, return a URI-style path - return - !filePath.StartsWith("untitled") && !filePath.StartsWith("inmemory") - ? new Uri("file://" + filePath).AbsoluteUri - : filePath; - } - - public static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) - { - return new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 - } - }; - } - - private static FileChange GetFileChangeDetails(Range changeRange, string insertString) - { - // The protocol's positions are zero-based so add 1 to all offsets - - if (changeRange == null) return new FileChange { InsertString = insertString, IsReload = true }; - - return new FileChange - { - InsertString = insertString, - Line = changeRange.Start.Line + 1, - Offset = changeRange.Start.Character + 1, - EndLine = changeRange.End.Line + 1, - EndOffset = changeRange.End.Character + 1, - IsReload = false - }; - } - - private Task RunScriptDiagnosticsAsync( - ScriptFile[] filesToAnalyze, - EditorSession editorSession, - EventContext eventContext) - { - return RunScriptDiagnosticsAsync(filesToAnalyze, editorSession, this.messageSender.SendEventAsync); - } - - private Task RunScriptDiagnosticsAsync( - ScriptFile[] filesToAnalyze, - EditorSession editorSession, - Func, PublishDiagnosticsNotification, Task> eventSender) - { - // If there's an existing task, attempt to cancel it - try - { - if (s_existingRequestCancellation != null) - { - // Try to cancel the request - s_existingRequestCancellation.Cancel(); - - // If cancellation didn't throw an exception, - // clean up the existing token - s_existingRequestCancellation.Dispose(); - s_existingRequestCancellation = null; - } - } - catch (Exception e) - { - // TODO: Catch a more specific exception! - Logger.Write( - LogLevel.Error, - string.Format( - "Exception while canceling analysis task:\n\n{0}", - e.ToString())); - - TaskCompletionSource cancelTask = new TaskCompletionSource(); - cancelTask.SetCanceled(); - return cancelTask.Task; - } - - // If filesToAnalzye is empty, nothing to do so return early. - if (filesToAnalyze.Length == 0) - { - return Task.FromResult(true); - } - - // Create a fresh cancellation token and then start the task. - // We create this on a different TaskScheduler so that we - // don't block the main message loop thread. - // TODO: Is there a better way to do this? - s_existingRequestCancellation = new CancellationTokenSource(); - Task.Factory.StartNew( - () => - DelayThenInvokeDiagnosticsAsync( - 750, - filesToAnalyze, - this.currentSettings.ScriptAnalysis?.Enable.Value ?? false, - this.codeActionsPerFile, - editorSession, - eventSender, - this.Logger, - s_existingRequestCancellation.Token), - CancellationToken.None, - TaskCreationOptions.None, - TaskScheduler.Default); - - return Task.FromResult(true); - } - - private static async Task DelayThenInvokeDiagnosticsAsync( - int delayMilliseconds, - ScriptFile[] filesToAnalyze, - bool isScriptAnalysisEnabled, - Dictionary> correctionIndex, - EditorSession editorSession, - EventContext eventContext, - ILogger Logger, - CancellationToken cancellationToken) - { - await DelayThenInvokeDiagnosticsAsync( - delayMilliseconds, - filesToAnalyze, - isScriptAnalysisEnabled, - correctionIndex, - editorSession, - eventContext.SendEventAsync, - Logger, - cancellationToken); - } - - private static async Task DelayThenInvokeDiagnosticsAsync( - int delayMilliseconds, - ScriptFile[] filesToAnalyze, - bool isScriptAnalysisEnabled, - Dictionary> correctionIndex, - EditorSession editorSession, - Func, PublishDiagnosticsNotification, Task> eventSender, - ILogger Logger, - CancellationToken cancellationToken) - { - // First of all, wait for the desired delay period before - // analyzing the provided list of files - try - { - await Task.Delay(delayMilliseconds, cancellationToken); - } - catch (TaskCanceledException) - { - // If the task is cancelled, exit directly - foreach (var script in filesToAnalyze) - { - await PublishScriptDiagnosticsAsync( - script, - script.DiagnosticMarkers, - correctionIndex, - eventSender); - } - - return; - } - - // If we've made it past the delay period then we don't care - // about the cancellation token anymore. This could happen - // when the user stops typing for long enough that the delay - // period ends but then starts typing while analysis is going - // on. It makes sense to send back the results from the first - // delay period while the second one is ticking away. - - // Get the requested files - foreach (ScriptFile scriptFile in filesToAnalyze) - { - List semanticMarkers = null; - if (isScriptAnalysisEnabled && editorSession.AnalysisService != null) - { - using (Logger.LogExecutionTime($"Script analysis of {scriptFile.FilePath} completed.")) - { - semanticMarkers = await editorSession.AnalysisService.GetSemanticMarkersAsync(scriptFile); - } - } - else - { - // Semantic markers aren't available if the AnalysisService - // isn't available - semanticMarkers = new List(); - } - - scriptFile.DiagnosticMarkers.AddRange(semanticMarkers); - - await PublishScriptDiagnosticsAsync( - scriptFile, - // Concat script analysis errors to any existing parse errors - scriptFile.DiagnosticMarkers, - correctionIndex, - eventSender); - } - } - - private async Task ClearMarkersAsync(ScriptFile scriptFile, EventContext eventContext) - { - // send empty diagnostic markers to clear any markers associated with the given file - await PublishScriptDiagnosticsAsync( - scriptFile, - new List(), - this.codeActionsPerFile, - eventContext); - } - - private static async Task PublishScriptDiagnosticsAsync( - ScriptFile scriptFile, - List markers, - Dictionary> correctionIndex, - EventContext eventContext) - { - await PublishScriptDiagnosticsAsync( - scriptFile, - markers, - correctionIndex, - eventContext.SendEventAsync); - } - - private static async Task PublishScriptDiagnosticsAsync( - ScriptFile scriptFile, - List markers, - Dictionary> correctionIndex, - Func, PublishDiagnosticsNotification, Task> eventSender) - { - List diagnostics = new List(); - - // Hold on to any corrections that may need to be applied later - Dictionary fileCorrections = - new Dictionary(); - - foreach (var marker in markers) - { - // Does the marker contain a correction? - Diagnostic markerDiagnostic = GetDiagnosticFromMarker(marker); - if (marker.Correction != null) - { - string diagnosticId = GetUniqueIdFromDiagnostic(markerDiagnostic); - fileCorrections.Add(diagnosticId, marker.Correction); - } - - diagnostics.Add(markerDiagnostic); - } - - correctionIndex[scriptFile.DocumentUri] = fileCorrections; - - // Always send syntax and semantic errors. We want to - // make sure no out-of-date markers are being displayed. - await eventSender( - PublishDiagnosticsNotification.Type, - new PublishDiagnosticsNotification - { - Uri = scriptFile.DocumentUri, - Diagnostics = diagnostics.ToArray() - }); - } - - // Generate a unique id that is used as a key to look up the associated code action (code fix) when - // we receive and process the textDocument/codeAction message. - private static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic) - { - Position start = diagnostic.Range.Start; - Position end = diagnostic.Range.End; - - var sb = new StringBuilder(256) - .Append(diagnostic.Source ?? "?") - .Append("_") - .Append(diagnostic.Code ?? "?") - .Append("_") - .Append(diagnostic.Severity?.ToString() ?? "?") - .Append("_") - .Append(start.Line) - .Append(":") - .Append(start.Character) - .Append("-") - .Append(end.Line) - .Append(":") - .Append(end.Character); - - var id = sb.ToString(); - return id; - } - - private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker) - { - return new Diagnostic - { - Severity = MapDiagnosticSeverity(scriptFileMarker.Level), - Message = scriptFileMarker.Message, - Code = scriptFileMarker.RuleName, - Source = scriptFileMarker.Source, - Range = new Range - { - Start = new Position - { - Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1, - Character = scriptFileMarker.ScriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptFileMarker.ScriptRegion.EndLineNumber - 1, - Character = scriptFileMarker.ScriptRegion.EndColumnNumber - 1 - } - } - }; - } - - private static CompletionItemKind MapCompletionKind(CompletionType completionType) - { - switch (completionType) - { - case CompletionType.Command: - return CompletionItemKind.Function; - - case CompletionType.Property: - return CompletionItemKind.Property; - - case CompletionType.Method: - return CompletionItemKind.Method; - - case CompletionType.Variable: - case CompletionType.ParameterName: - return CompletionItemKind.Variable; - - case CompletionType.File: - return CompletionItemKind.File; - - case CompletionType.Folder: - return CompletionItemKind.Folder; - - default: - return CompletionItemKind.Text; - } - } - - private static CompletionItem CreateCompletionItem( - CompletionDetails completionDetails, - BufferRange completionRange, - int sortIndex) - { - string detailString = null; - string documentationString = null; - string completionText = completionDetails.CompletionText; - InsertTextFormat insertTextFormat = InsertTextFormat.PlainText; - - if ((completionDetails.CompletionType == CompletionType.Variable) || - (completionDetails.CompletionType == CompletionType.ParameterName)) - { - // Look for type encoded in the tooltip for parameters and variables. - // Display PowerShell type names in [] to be consistent with PowerShell syntax - // and now the debugger displays type names. - var matches = Regex.Matches(completionDetails.ToolTipText, @"^(\[.+\])"); - if ((matches.Count > 0) && (matches[0].Groups.Count > 1)) - { - detailString = matches[0].Groups[1].Value; - } - } - else if ((completionDetails.CompletionType == CompletionType.Method) || - (completionDetails.CompletionType == CompletionType.Property)) - { - // We have a raw signature for .NET members, heck let's display it. It's - // better than nothing. - documentationString = completionDetails.ToolTipText; - } - else if (completionDetails.CompletionType == CompletionType.Command) - { - // For Commands, let's extract the resolved command or the path for an exe - // from the ToolTipText - if there is any ToolTipText. - if (completionDetails.ToolTipText != null) - { - // Fix for #240 - notepad++.exe in tooltip text caused regex parser to throw. - string escapedToolTipText = Regex.Escape(completionDetails.ToolTipText); - - // Don't display ToolTipText if it is the same as the ListItemText. - // Reject command syntax ToolTipText - it's too much to display as a detailString. - if (!completionDetails.ListItemText.Equals( - completionDetails.ToolTipText, - StringComparison.OrdinalIgnoreCase) && - !Regex.IsMatch(completionDetails.ToolTipText, - @"^\s*" + escapedToolTipText + @"\s+\[")) - { - detailString = completionDetails.ToolTipText; - } - } - } - else if ((completionDetails.CompletionType == CompletionType.Folder) && - (completionText.EndsWith("\"") || completionText.EndsWith("'"))) - { - // Insert a final "tab stop" as identified by $0 in the snippet provided for completion. - // For folder paths, we take the path returned by PowerShell e.g. 'C:\Program Files' and insert - // the tab stop marker before the closing quote char e.g. 'C:\Program Files$0'. - // This causes the editing cursor to be placed *before* the final quote after completion, - // which makes subsequent path completions work. See this part of the LSP spec for details: - // https://microsoft.github.io/language-server-protocol/specification#textDocument_completion - int len = completionDetails.CompletionText.Length; - completionText = completionDetails.CompletionText.Insert(len - 1, "$0"); - insertTextFormat = InsertTextFormat.Snippet; - } - - // Force the client to maintain the sort order in which the - // original completion results were returned. We just need to - // make sure the default order also be the lexicographical order - // which we do by prefixing the ListItemText with a leading 0's - // four digit index. - var sortText = $"{sortIndex:D4}{completionDetails.ListItemText}"; - - return new CompletionItem - { - InsertText = completionText, - InsertTextFormat = insertTextFormat, - Label = completionDetails.ListItemText, - Kind = MapCompletionKind(completionDetails.CompletionType), - Detail = detailString, - Documentation = documentationString, - SortText = sortText, - FilterText = completionDetails.CompletionText, - TextEdit = new TextEdit - { - NewText = completionText, - Range = new Range - { - Start = new Position - { - Line = completionRange.Start.Line - 1, - Character = completionRange.Start.Column - 1 - }, - End = new Position - { - Line = completionRange.End.Line - 1, - Character = completionRange.End.Column - 1 - } - } - } - }; - } - - private static DiagnosticSeverity MapDiagnosticSeverity(ScriptFileMarkerLevel markerLevel) - { - switch (markerLevel) - { - case ScriptFileMarkerLevel.Error: - return DiagnosticSeverity.Error; - - case ScriptFileMarkerLevel.Warning: - return DiagnosticSeverity.Warning; - - case ScriptFileMarkerLevel.Information: - return DiagnosticSeverity.Information; - - default: - return DiagnosticSeverity.Error; - } - } - - private static ParameterInformation CreateParameterInfo(ParameterInfo parameterInfo) - { - return new ParameterInformation - { - Label = parameterInfo.Name, - Documentation = string.Empty - }; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs deleted file mode 100644 index 5df247bd5..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs +++ /dev/null @@ -1,224 +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 Microsoft.PowerShell.EditorServices; -using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - internal class LanguageServerEditorOperations : IEditorOperations - { - private const bool DefaultPreviewSetting = true; - - private EditorSession editorSession; - private IMessageSender messageSender; - - public LanguageServerEditorOperations( - EditorSession editorSession, - IMessageSender messageSender) - { - this.editorSession = editorSession; - this.messageSender = messageSender; - } - - public async Task GetEditorContextAsync() - { - ClientEditorContext clientContext = - await this.messageSender.SendRequestAsync( - GetEditorContextRequest.Type, - new GetEditorContextRequest(), - true); - - return this.ConvertClientEditorContext(clientContext); - } - - public async Task InsertTextAsync(string filePath, string text, BufferRange insertRange) - { - await this.messageSender.SendRequestAsync( - InsertTextRequest.Type, - 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 - } - } - }, false); - - // TODO: Set the last param back to true! - } - - public Task SetSelectionAsync(BufferRange selectionRange) - { - return this.messageSender.SendRequestAsync( - SetSelectionRequest.Type, - 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 - } - } - }, true); - } - - public EditorContext ConvertClientEditorContext( - ClientEditorContext clientContext) - { - ScriptFile scriptFile = this.editorSession.Workspace.CreateScriptFileFromFileBuffer( - clientContext.CurrentFilePath, - clientContext.CurrentFileContent); - - return - new EditorContext( - this, - scriptFile, - new BufferPosition( - clientContext.CursorPosition.Line + 1, - clientContext.CursorPosition.Character + 1), - new BufferRange( - clientContext.SelectionRange.Start.Line + 1, - clientContext.SelectionRange.Start.Character + 1, - clientContext.SelectionRange.End.Line + 1, - clientContext.SelectionRange.End.Character + 1), - clientContext.CurrentFileLanguage); - } - - public Task NewFileAsync() - { - return - this.messageSender.SendRequestAsync( - NewFileRequest.Type, - null, - true); - } - - public Task OpenFileAsync(string filePath) - { - return - this.messageSender.SendRequestAsync( - OpenFileRequest.Type, - new OpenFileDetails - { - FilePath = filePath, - Preview = DefaultPreviewSetting - }, - true); - } - - public Task OpenFileAsync(string filePath, bool preview) - { - return - this.messageSender.SendRequestAsync( - OpenFileRequest.Type, - new OpenFileDetails - { - FilePath = filePath, - Preview = preview - }, - true); - } - - public Task CloseFileAsync(string filePath) - { - return - this.messageSender.SendRequestAsync( - CloseFileRequest.Type, - filePath, - true); - } - - public Task SaveFileAsync(string filePath) - { - return SaveFileAsync(filePath, null); - } - - public Task SaveFileAsync(string currentPath, string newSavePath) - { - return - this.messageSender.SendRequestAsync( - SaveFileRequest.Type, - new SaveFileDetails - { - FilePath = currentPath, - NewPath = newSavePath - }, - true); - } - - public string GetWorkspacePath() - { - return this.editorSession.Workspace.WorkspacePath; - } - - public string GetWorkspaceRelativePath(string filePath) - { - return this.editorSession.Workspace.GetRelativePath(filePath); - } - - public Task ShowInformationMessageAsync(string message) - { - return - this.messageSender.SendRequestAsync( - ShowInformationMessageRequest.Type, - message, - true); - } - - public Task ShowErrorMessageAsync(string message) - { - return - this.messageSender.SendRequestAsync( - ShowErrorMessageRequest.Type, - message, - true); - } - - public Task ShowWarningMessageAsync(string message) - { - return - this.messageSender.SendRequestAsync( - ShowWarningMessageRequest.Type, - message, - true); - } - - public Task SetStatusBarMessageAsync(string message, int? timeout) - { - return - this.messageSender.SendRequestAsync( - SetStatusBarMessageRequest.Type, - new StatusBarMessageDetails - { - Message = message, - Timeout = timeout - }, - true); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs deleted file mode 100644 index 8ce27bcfb..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs +++ /dev/null @@ -1,382 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Security; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - public class LanguageServerSettings - { - public bool EnableProfileLoading { get; set; } - - public ScriptAnalysisSettings ScriptAnalysis { get; set; } - - public CodeFormattingSettings CodeFormatting { get; set; } - - public CodeFoldingSettings CodeFolding { get; set; } - - public LanguageServerSettings() - { - this.ScriptAnalysis = new ScriptAnalysisSettings(); - this.CodeFormatting = new CodeFormattingSettings(); - this.CodeFolding = new CodeFoldingSettings(); - } - - public void Update( - LanguageServerSettings settings, - string workspaceRootPath, - ILogger logger) - { - if (settings != null) - { - this.EnableProfileLoading = settings.EnableProfileLoading; - this.ScriptAnalysis.Update( - settings.ScriptAnalysis, - workspaceRootPath, - logger); - this.CodeFormatting = new CodeFormattingSettings(settings.CodeFormatting); - this.CodeFolding.Update(settings.CodeFolding, logger); - } - } - } - - public class ScriptAnalysisSettings - { - public bool? Enable { get; set; } - - public string SettingsPath { get; set; } - - public ScriptAnalysisSettings() - { - this.Enable = true; - } - - public void Update( - ScriptAnalysisSettings settings, - string workspaceRootPath, - ILogger logger) - { - if (settings != null) - { - this.Enable = settings.Enable; - - string settingsPath = settings.SettingsPath; - - try - { - if (string.IsNullOrWhiteSpace(settingsPath)) - { - settingsPath = null; - } - else if (!Path.IsPathRooted(settingsPath)) - { - if (string.IsNullOrEmpty(workspaceRootPath)) - { - // The workspace root path could be an empty string - // when the user has opened a PowerShell script file - // without opening an entire folder (workspace) first. - // In this case we should just log an error and let - // the specified settings path go through even though - // it will fail to load. - logger.Write( - LogLevel.Error, - "Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath."); - } - else - { - settingsPath = Path.GetFullPath(Path.Combine(workspaceRootPath, settingsPath)); - } - } - - this.SettingsPath = settingsPath; - logger.Write(LogLevel.Verbose, $"Using Script Analyzer settings path - '{settingsPath ?? ""}'."); - } - catch (Exception ex) when ( - ex is NotSupportedException || - ex is PathTooLongException || - ex is SecurityException) - { - // Invalid chars in path like ${env:HOME} can cause Path.GetFullPath() to throw, catch such errors here - logger.WriteException( - $"Invalid Script Analyzer settings path - '{settingsPath}'.", - ex); - - this.SettingsPath = null; - } - } - } - } - - /// - /// Code formatting presets. - /// See https://en.wikipedia.org/wiki/Indent_style for details on indent and brace styles. - /// - public enum CodeFormattingPreset - { - /// - /// Use the formatting settings as-is. - /// - Custom, - - /// - /// Configure the formatting settings to resemble the Allman indent/brace style. - /// - Allman, - - /// - /// Configure the formatting settings to resemble the one true brace style variant of K&R indent/brace style. - /// - OTBS, - - /// - /// Configure the formatting settings to resemble the Stroustrup brace style variant of K&R indent/brace style. - /// - Stroustrup - } - - /// - /// Multi-line pipeline style settings. - /// - public enum PipelineIndentationStyle - { - /// - /// After the indentation level only once after the first pipeline and keep this level for the following pipelines. - /// - IncreaseIndentationForFirstPipeline, - - /// - /// After every pipeline, keep increasing the indentation. - /// - IncreaseIndentationAfterEveryPipeline, - - /// - /// Do not increase indentation level at all after pipeline. - /// - NoIndentation - } - - public class CodeFormattingSettings - { - /// - /// Default constructor. - /// > - public CodeFormattingSettings() - { - - } - - /// - /// Copy constructor. - /// - /// An instance of type CodeFormattingSettings. - public CodeFormattingSettings(CodeFormattingSettings codeFormattingSettings) - { - if (codeFormattingSettings == null) - { - throw new ArgumentNullException(nameof(codeFormattingSettings)); - } - - foreach (var prop in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - prop.SetValue(this, prop.GetValue(codeFormattingSettings)); - } - } - - public CodeFormattingPreset Preset { get; set; } - public bool OpenBraceOnSameLine { get; set; } - public bool NewLineAfterOpenBrace { get; set; } - public bool NewLineAfterCloseBrace { get; set; } - public PipelineIndentationStyle PipelineIndentationStyle { get; set; } - public bool WhitespaceBeforeOpenBrace { get; set; } - public bool WhitespaceBeforeOpenParen { get; set; } - public bool WhitespaceAroundOperator { get; set; } - public bool WhitespaceAfterSeparator { get; set; } - public bool WhitespaceInsideBrace { get; set; } - public bool WhitespaceAroundPipe { get; set; } - public bool IgnoreOneLineBlock { get; set; } - public bool AlignPropertyValuePairs { get; set; } - public bool UseCorrectCasing { get; set; } - - - /// - /// Get the settings hashtable that will be consumed by PSScriptAnalyzer. - /// - /// The tab size in the number spaces. - /// If true, insert spaces otherwise insert tabs for indentation. - /// - public Hashtable GetPSSASettingsHashtable( - int tabSize, - bool insertSpaces) - { - var settings = GetCustomPSSASettingsHashtable(tabSize, insertSpaces); - var ruleSettings = (Hashtable)(settings["Rules"]); - var closeBraceSettings = (Hashtable)ruleSettings["PSPlaceCloseBrace"]; - var openBraceSettings = (Hashtable)ruleSettings["PSPlaceOpenBrace"]; - switch(Preset) - { - case CodeFormattingPreset.Allman: - openBraceSettings["OnSameLine"] = false; - openBraceSettings["NewLineAfter"] = true; - closeBraceSettings["NewLineAfter"] = true; - break; - - case CodeFormattingPreset.OTBS: - openBraceSettings["OnSameLine"] = true; - openBraceSettings["NewLineAfter"] = true; - closeBraceSettings["NewLineAfter"] = false; - break; - - case CodeFormattingPreset.Stroustrup: - openBraceSettings["OnSameLine"] = true; - openBraceSettings["NewLineAfter"] = true; - closeBraceSettings["NewLineAfter"] = true; - break; - - default: - break; - } - - return settings; - } - - private Hashtable GetCustomPSSASettingsHashtable(int tabSize, bool insertSpaces) - { - return new Hashtable - { - {"IncludeRules", new string[] { - "PSPlaceCloseBrace", - "PSPlaceOpenBrace", - "PSUseConsistentWhitespace", - "PSUseConsistentIndentation", - "PSAlignAssignmentStatement" - }}, - {"Rules", new Hashtable { - {"PSPlaceOpenBrace", new Hashtable { - {"Enable", true}, - {"OnSameLine", OpenBraceOnSameLine}, - {"NewLineAfter", NewLineAfterOpenBrace}, - {"IgnoreOneLineBlock", IgnoreOneLineBlock} - }}, - {"PSPlaceCloseBrace", new Hashtable { - {"Enable", true}, - {"NewLineAfter", NewLineAfterCloseBrace}, - {"IgnoreOneLineBlock", IgnoreOneLineBlock} - }}, - {"PSUseConsistentIndentation", new Hashtable { - {"Enable", true}, - {"IndentationSize", tabSize}, - {"PipelineIndentation", PipelineIndentationStyle }, - {"Kind", insertSpaces ? "space" : "tab"} - }}, - {"PSUseConsistentWhitespace", new Hashtable { - {"Enable", true}, - {"CheckOpenBrace", WhitespaceBeforeOpenBrace}, - {"CheckOpenParen", WhitespaceBeforeOpenParen}, - {"CheckOperator", WhitespaceAroundOperator}, - {"CheckSeparator", WhitespaceAfterSeparator}, - {"CheckInnerBrace", WhitespaceInsideBrace}, - {"CheckPipe", WhitespaceAroundPipe}, - }}, - {"PSAlignAssignmentStatement", new Hashtable { - {"Enable", true}, - {"CheckHashtable", AlignPropertyValuePairs} - }}, - {"PSUseCorrectCasing", new Hashtable { - {"Enable", UseCorrectCasing} - }}, - }} - }; - } - } - - /// - /// Code folding settings - /// - public class CodeFoldingSettings - { - /// - /// Whether the folding is enabled. Default is true as per VSCode - /// - public bool Enable { get; set; } = true; - - /// - /// Whether to show or hide the last line of a folding region. Default is true as per VSCode - /// - public bool ShowLastLine { get; set; } = true; - - /// - /// Update these settings from another settings object - /// - public void Update( - CodeFoldingSettings settings, - ILogger logger) - { - if (settings != null) { - if (this.Enable != settings.Enable) { - this.Enable = settings.Enable; - logger.Write(LogLevel.Verbose, string.Format("Using Code Folding Enabled - {0}", this.Enable)); - } - if (this.ShowLastLine != settings.ShowLastLine) { - this.ShowLastLine = settings.ShowLastLine; - logger.Write(LogLevel.Verbose, string.Format("Using Code Folding ShowLastLine - {0}", this.ShowLastLine)); - } - } - } - } - - /// - /// Additional settings from the Language Client that affect Language Server operations but - /// do not exist under the 'powershell' section - /// - public class EditorFileSettings - { - /// - /// Exclude files globs consists of hashtable with the key as the glob and a boolean value to indicate if the - /// the glob is in effect. - /// - public Dictionary Exclude { get; set; } - } - - /// - /// Additional settings from the Language Client that affect Language Server operations but - /// do not exist under the 'powershell' section - /// - public class EditorSearchSettings - { - /// - /// Exclude files globs consists of hashtable with the key as the glob and a boolean value to indicate if the - /// the glob is in effect. - /// - public Dictionary Exclude { get; set; } - /// - /// Whether to follow symlinks when searching - /// - public bool FollowSymlinks { get; set; } = true; - } - - public class LanguageServerSettingsWrapper - { - // NOTE: This property is capitalized as 'Powershell' because the - // mode name sent from the client is written as 'powershell' and - // JSON.net is using camelCasing. - public LanguageServerSettings Powershell { get; set; } - - // NOTE: This property is capitalized as 'Files' because the - // mode name sent from the client is written as 'files' and - // JSON.net is using camelCasing. - public EditorFileSettings Files { get; set; } - - // NOTE: This property is capitalized as 'Search' because the - // mode name sent from the client is written as 'search' and - // JSON.net is using camelCasing. - public EditorSearchSettings Search { get; set; } - } -} diff --git a/src/PowerShellEditorServices.Protocol/Server/OutputDebouncer.cs b/src/PowerShellEditorServices.Protocol/Server/OutputDebouncer.cs deleted file mode 100644 index 079183a26..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/OutputDebouncer.cs +++ /dev/null @@ -1,102 +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 Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - /// - /// Throttles output written via OutputEvents by batching all output - /// written within a short time window and writing it all out at once. - /// - public class OutputDebouncer : AsyncDebouncer - { - #region Private Fields - - private IMessageSender messageSender; - private bool currentOutputIsError = false; - private string currentOutputString = null; - - #endregion - - #region Constants - - // Set a really short window for output flushes. This - // gives the appearance of fast output without the crushing - // overhead of sending an OutputEvent for every single line - // written. At this point it seems that around 10-20 lines get - // batched for each flush when Get-Process is called. - public const int OutputFlushInterval = 200; - - #endregion - - #region Constructors - - public OutputDebouncer(IMessageSender messageSender) - : base(OutputFlushInterval, false) - { - this.messageSender = messageSender; - } - - #endregion - - #region Private Methods - - protected override async Task OnInvokeAsync(OutputWrittenEventArgs output) - { - bool outputIsError = output.OutputType == OutputType.Error; - - if (this.currentOutputIsError != outputIsError) - { - if (this.currentOutputString != null) - { - // Flush the output - await this.OnFlushAsync(); - } - - this.currentOutputString = string.Empty; - this.currentOutputIsError = outputIsError; - } - - // Output string could be null if the last output was already flushed - if (this.currentOutputString == null) - { - this.currentOutputString = string.Empty; - } - - // Add to string (and include newline) - this.currentOutputString += - output.OutputText + - (output.IncludeNewLine ? - System.Environment.NewLine : - string.Empty); - } - - protected override async Task OnFlushAsync() - { - // Only flush output if there is some to flush - if (this.currentOutputString != null) - { - // Send an event for the current output - await this.messageSender.SendEventAsync( - OutputEvent.Type, - new OutputEventBody - { - Output = this.currentOutputString, - Category = (this.currentOutputIsError) ? "stderr" : "stdout" - }); - - // Clear the output string for the next batch - this.currentOutputString = null; - } - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices.VSCode/Cmdlets/VSCodeHtmlContentViewCommands.cs b/src/PowerShellEditorServices.VSCode/Cmdlets/VSCodeHtmlContentViewCommands.cs new file mode 100644 index 000000000..d26162743 --- /dev/null +++ b/src/PowerShellEditorServices.VSCode/Cmdlets/VSCodeHtmlContentViewCommands.cs @@ -0,0 +1,238 @@ +// +// 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.Management.Automation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Engine.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.VSCode.CustomViews; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.VSCode +{ + /// + [Cmdlet(VerbsCommon.New,"VSCodeHtmlContentView")] + [OutputType(typeof(IHtmlContentView))] + public class NewVSCodeHtmlContentViewCommand : PSCmdlet + { + private HtmlContentViewsFeature _htmlContentViewsFeature; + + private ILogger _logger; + + private ViewColumn? _showInColumn; + + /// + [Parameter(Mandatory = true, Position = 0)] + [ValidateNotNullOrEmpty] + public string Title { get; set; } + + /// + [Parameter(Position = 1)] + public ViewColumn ShowInColumn + { + get => _showInColumn.GetValueOrDefault(); + set => _showInColumn = value; + } + + /// + protected override void BeginProcessing() + { + if (_htmlContentViewsFeature == null) + { + if (GetVariableValue("psEditor") is EditorObject psEditor) + { + _logger = psEditor.Components.GetService().CreateLogger("PowerShellEditorServices.VSCode"); + + _htmlContentViewsFeature = new HtmlContentViewsFeature( + psEditor.Components.GetService(), + _logger); + + _logger.LogInformation("PowerShell Editor Services VS Code module loaded."); + } + else + { + ThrowTerminatingError( + new ErrorRecord( + new ItemNotFoundException("Cannot find the '$psEditor' variable."), + "PSEditorNotFound", + ErrorCategory.ObjectNotFound, + targetObject: null)); + return; + } + } + + IHtmlContentView view = _htmlContentViewsFeature.CreateHtmlContentViewAsync(Title) + .GetAwaiter() + .GetResult(); + + if (_showInColumn != null) { + try + { + view.Show(_showInColumn.Value).GetAwaiter().GetResult(); + } + catch (Exception e) + { + WriteError( + new ErrorRecord( + e, + "HtmlContentViewCouldNotShow", + ErrorCategory.OpenError, + targetObject: null)); + + return; + } + } + + WriteObject(view); + } + } + + /// + [Cmdlet(VerbsCommon.Set,"VSCodeHtmlContentView")] + public class SetVSCodeHtmlContentViewCommand : PSCmdlet + { + /// + [Parameter(Mandatory = true, Position = 0)] + [Alias("View")] + [ValidateNotNull] + public IHtmlContentView HtmlContentView { get; set; } + + /// + [Parameter(Mandatory = true, Position = 1)] + [Alias("Content")] + [AllowEmptyString] + public string HtmlBodyContent { get; set; } + + /// + [Parameter(Position = 2)] + public string[] JavaScriptPaths { get; set; } + + /// + [Parameter(Position = 3)] + public string[] StyleSheetPaths { get; set; } + + /// + protected override void BeginProcessing() + { + var htmlContent = new HtmlContent(); + htmlContent.BodyContent = HtmlBodyContent; + htmlContent.JavaScriptPaths = JavaScriptPaths; + htmlContent.StyleSheetPaths = StyleSheetPaths; + try + { + HtmlContentView.SetContentAsync(htmlContent).GetAwaiter().GetResult(); + } + catch (Exception e) + { + WriteError( + new ErrorRecord( + e, + "HtmlContentViewCouldNotSet", + ErrorCategory.WriteError, + targetObject: null)); + } + } + } + + /// + [Cmdlet(VerbsCommon.Close,"VSCodeHtmlContentView")] + public class CloseVSCodeHtmlContentViewCommand : PSCmdlet + { + /// + [Parameter(Mandatory = true, Position = 0)] + [Alias("View")] + [ValidateNotNull] + public IHtmlContentView HtmlContentView { get; set; } + + /// + protected override void BeginProcessing() + { + try + { + HtmlContentView.Close().GetAwaiter().GetResult(); + } + catch (Exception e) + { + WriteError( + new ErrorRecord( + e, + "HtmlContentViewCouldNotClose", + ErrorCategory.CloseError, + targetObject: null)); + } + } + } + + /// + [Cmdlet(VerbsCommon.Show,"VSCodeHtmlContentView")] + public class ShowVSCodeHtmlContentViewCommand : PSCmdlet + { + /// + [Parameter(Mandatory = true, Position = 0)] + [Alias("View")] + [ValidateNotNull] + public IHtmlContentView HtmlContentView { get; set; } + + /// + [Parameter(Position = 1)] + [Alias("Column")] + [ValidateNotNull] + public ViewColumn ViewColumn { get; set; } = ViewColumn.One; + + /// + protected override void BeginProcessing() + { + try + { + HtmlContentView.Show(ViewColumn).GetAwaiter().GetResult(); + } + catch (Exception e) + { + WriteError( + new ErrorRecord( + e, + "HtmlContentViewCouldNotShow", + ErrorCategory.OpenError, + targetObject: null)); + } + } + } + + /// + [Cmdlet(VerbsCommunications.Write,"VSCodeHtmlContentView")] + public class WriteVSCodeHtmlContentViewCommand : PSCmdlet + { + /// + [Parameter(Mandatory = true, Position = 0)] + [Alias("View")] + [ValidateNotNull] + public IHtmlContentView HtmlContentView { get; set; } + + /// + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 1)] + [Alias("Content")] + [ValidateNotNull] + public string AppendedHtmlBodyContent { get; set; } + + /// + protected override void ProcessRecord() + { + try + { + HtmlContentView.AppendContentAsync(AppendedHtmlBodyContent).GetAwaiter().GetResult(); + } + catch (Exception e) + { + WriteError( + new ErrorRecord( + e, + "HtmlContentViewCouldNotWrite", + ErrorCategory.WriteError, + targetObject: null)); + } + } + } +} diff --git a/src/PowerShellEditorServices.VSCode/ComponentRegistration.cs b/src/PowerShellEditorServices.VSCode/ComponentRegistration.cs deleted file mode 100644 index 5ea4422cd..000000000 --- a/src/PowerShellEditorServices.VSCode/ComponentRegistration.cs +++ /dev/null @@ -1,41 +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 Microsoft.PowerShell.EditorServices.Components; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; -using Microsoft.PowerShell.EditorServices.VSCode.CustomViews; - -namespace Microsoft.PowerShell.EditorServices.VSCode -{ - /// - /// Methods for registering components from this module into - /// the editor session. - /// - public static class ComponentRegistration - { - /// - /// Registers the feature components in this module with the - /// host editor. - /// - /// - /// The IComponentRegistry where feature components will be registered. - /// - public static void Register(IComponentRegistry components) - { - ILogger logger = components.Get(); - - components.Register( - new HtmlContentViewsFeature( - components.Get(), - logger)); - - logger.Write( - LogLevel.Normal, - "PowerShell Editor Services VS Code module loaded."); - } - } -} diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewBase.cs b/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewBase.cs index 1fdb6ddf0..524ea181b 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewBase.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewBase.cs @@ -5,14 +5,15 @@ using System; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { internal abstract class CustomViewBase : ICustomView { - protected IMessageSender messageSender; + protected ILanguageServer languageServer; + protected ILogger logger; public Guid Id { get; private set; } @@ -24,50 +25,50 @@ internal abstract class CustomViewBase : ICustomView public CustomViewBase( string viewTitle, CustomViewType viewType, - IMessageSender messageSender, + ILanguageServer languageServer, ILogger logger) { this.Id = Guid.NewGuid(); this.Title = viewTitle; this.ViewType = viewType; - this.messageSender = messageSender; + this.languageServer = languageServer; this.logger = logger; } - internal Task CreateAsync() + internal async Task CreateAsync() { - return - this.messageSender.SendRequestAsync( - NewCustomViewRequest.Type, - new NewCustomViewRequest - { - Id = this.Id, - Title = this.Title, - ViewType = this.ViewType, - }, true); + await languageServer.SendRequest( + NewCustomViewRequest.Method, + new NewCustomViewRequest + { + Id = this.Id, + Title = this.Title, + ViewType = this.ViewType, + } + ); } - public Task Show(ViewColumn viewColumn) + public async Task Show(ViewColumn viewColumn) { - return - this.messageSender.SendRequestAsync( - ShowCustomViewRequest.Type, - new ShowCustomViewRequest - { - Id = this.Id, - ViewColumn = viewColumn - }, true); + await languageServer.SendRequest( + ShowCustomViewRequest.Method, + new ShowCustomViewRequest + { + Id = this.Id, + ViewColumn = viewColumn + } + ); } - public Task Close() + public async Task Close() { - return - this.messageSender.SendRequestAsync( - CloseCustomViewRequest.Type, - new CloseCustomViewRequest - { - Id = this.Id, - }, true); + await languageServer.SendRequest( + CloseCustomViewRequest.Method, + new CloseCustomViewRequest + { + Id = this.Id, + } + ); } } } diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewFeature.cs b/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewFeature.cs index ac65d02fa..0e6c9a85f 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewFeature.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewFeature.cs @@ -5,25 +5,25 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { internal abstract class CustomViewFeatureBase where TView : ICustomView { - protected IMessageSender messageSender; + protected ILanguageServer languageServer; + protected ILogger logger; private readonly Dictionary viewIndex; public CustomViewFeatureBase( - IMessageSender messageSender, + ILanguageServer languageServer, ILogger logger) { this.viewIndex = new Dictionary(); - this.messageSender = messageSender; + this.languageServer = languageServer; this.logger = logger; } diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewMessages.cs b/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewMessages.cs index d87a5e7f7..a2a3cd5e8 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewMessages.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/CustomViewMessages.cs @@ -4,8 +4,6 @@ // using System; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { @@ -15,11 +13,9 @@ namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews public class NewCustomViewRequest { /// - /// The RequestType for this request. + /// The Language Server Protocol 'method'. /// - public static readonly - RequestType Type = - RequestType.Create("powerShell/newCustomView"); + public static string Method => "powerShell/newCustomView"; /// /// Gets or sets the Id of the view. @@ -43,11 +39,9 @@ public static readonly public class ShowCustomViewRequest { /// - /// The RequestType for this request. + /// The Language Server Protocol 'method'. /// - public static readonly - RequestType Type = - RequestType.Create("powerShell/showCustomView"); + public static string Method => "powerShell/showCustomView"; /// /// Gets or sets the Id of the view. @@ -66,11 +60,9 @@ public static readonly public class CloseCustomViewRequest { /// - /// The RequestType for this request. + /// The Language Server Protocol 'method'. /// - public static readonly - RequestType Type = - RequestType.Create("powerShell/closeCustomView"); + public static string Method => "powerShell/closeCustomView"; /// /// Gets or sets the Id of the view. diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentView.cs b/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentView.cs index c7a553707..b615e58fe 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentView.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentView.cs @@ -7,8 +7,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { @@ -16,29 +16,29 @@ internal class HtmlContentView : CustomViewBase, IHtmlContentView { public HtmlContentView( string viewTitle, - IMessageSender messageSender, + ILanguageServer languageServer, ILogger logger) : base( viewTitle, CustomViewType.HtmlContent, - messageSender, + languageServer, logger) { } - public Task SetContentAsync(string htmlBodyContent) + public async Task SetContentAsync(string htmlBodyContent) { - return - this.messageSender.SendRequestAsync( - SetHtmlContentViewRequest.Type, - new SetHtmlContentViewRequest - { - Id = this.Id, - HtmlContent = new HtmlContent { BodyContent = htmlBodyContent } - }, true); + await languageServer.SendRequest( + SetHtmlContentViewRequest.Method, + new SetHtmlContentViewRequest + { + Id = this.Id, + HtmlContent = new HtmlContent { BodyContent = htmlBodyContent } + } + ); } - public Task SetContentAsync(HtmlContent htmlContent) + public async Task SetContentAsync(HtmlContent htmlContent) { HtmlContent validatedContent = new HtmlContent() @@ -48,26 +48,26 @@ public Task SetContentAsync(HtmlContent htmlContent) StyleSheetPaths = this.GetUriPaths(htmlContent.StyleSheetPaths) }; - return - this.messageSender.SendRequestAsync( - SetHtmlContentViewRequest.Type, - new SetHtmlContentViewRequest - { - Id = this.Id, - HtmlContent = validatedContent - }, true); + await languageServer.SendRequest( + SetHtmlContentViewRequest.Method, + new SetHtmlContentViewRequest + { + Id = this.Id, + HtmlContent = validatedContent + } + ); } - public Task AppendContentAsync(string appendedHtmlBodyContent) + public async Task AppendContentAsync(string appendedHtmlBodyContent) { - return - this.messageSender.SendRequestAsync( - AppendHtmlContentViewRequest.Type, - new AppendHtmlContentViewRequest - { - Id = this.Id, - AppendedHtmlBodyContent = appendedHtmlBodyContent - }, true); + await languageServer.SendRequest( + AppendHtmlContentViewRequest.Method, + new AppendHtmlContentViewRequest + { + Id = this.Id, + AppendedHtmlBodyContent = appendedHtmlBodyContent + } + ); } private string[] GetUriPaths(string[] filePaths) diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewMessages.cs b/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewMessages.cs index 09e91b621..c982ca945 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewMessages.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewMessages.cs @@ -4,7 +4,6 @@ // using System; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { @@ -14,11 +13,9 @@ namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews public class SetHtmlContentViewRequest { /// - /// The RequestType for this request. + /// The Language Server Protocol 'method'. /// - public static readonly - RequestType Type = - RequestType.Create("powerShell/setHtmlViewContent"); + public static string Method => "powerShell/setHtmlViewContent"; /// /// Gets or sets the Id of the view. @@ -37,11 +34,9 @@ public static readonly public class AppendHtmlContentViewRequest { /// - /// The RequestType for this request. + /// The Language Server Protocol 'method'. /// - public static readonly - RequestType Type = - RequestType.Create("powerShell/appendHtmlViewContent"); + public static string Method => "powerShell/appendHtmlViewContent"; /// /// Gets or sets the Id of the view. diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewsFeature.cs b/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewsFeature.cs index a520fa610..e23d1b552 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewsFeature.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/HtmlContentViewsFeature.cs @@ -3,19 +3,18 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { internal class HtmlContentViewsFeature : CustomViewFeatureBase, IHtmlContentViews { public HtmlContentViewsFeature( - IMessageSender messageSender, + ILanguageServer languageServer, ILogger logger) - : base(messageSender, logger) + : base(languageServer, logger) { } @@ -24,7 +23,7 @@ public async Task CreateHtmlContentViewAsync(string viewTitle) HtmlContentView htmlView = new HtmlContentView( viewTitle, - this.messageSender, + this.languageServer, this.logger); await htmlView.CreateAsync(); diff --git a/src/PowerShellEditorServices.VSCode/CustomViews/ICustomView.cs b/src/PowerShellEditorServices.VSCode/CustomViews/ICustomView.cs index 5b52781b3..a9561770c 100644 --- a/src/PowerShellEditorServices.VSCode/CustomViews/ICustomView.cs +++ b/src/PowerShellEditorServices.VSCode/CustomViews/ICustomView.cs @@ -5,8 +5,6 @@ using System; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.VSCode.CustomViews { diff --git a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj index c50e535ea..54a39becd 100644 --- a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj +++ b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj @@ -15,9 +15,10 @@ - - - + + + All + diff --git a/src/PowerShellEditorServices/Analysis/AnalysisOutputWriter.cs b/src/PowerShellEditorServices/Analysis/AnalysisOutputWriter.cs deleted file mode 100644 index 4cfe4dc7d..000000000 --- a/src/PowerShellEditorServices/Analysis/AnalysisOutputWriter.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#if ScriptAnalyzerLogger -using Microsoft.Windows.PowerShell.ScriptAnalyzer; -using System; -using System.Management.Automation; -using Microsoft.PowerShell.EditorServices.Console; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides an implementation of ScriptAnalyzer's IOutputWriter - /// interface that writes to trace logs. - /// - - internal class AnalysisOutputWriter : IOutputWriter - { - private IConsoleHost consoleHost; - - public AnalysisOutputWriter(IConsoleHost consoleHost) - { - this.consoleHost = consoleHost; - } - - #region IOutputWriter Implementation - - void IOutputWriter.WriteError(ErrorRecord error) - { - this.consoleHost?.WriteOutput(error.ToString(), true, OutputType.Error, ConsoleColor.Red, ConsoleColor.Black); - } - - void IOutputWriter.WriteWarning(string message) - { - this.consoleHost?.WriteOutput(message, true, OutputType.Warning, ConsoleColor.Yellow, ConsoleColor.Black); - } - - void IOutputWriter.WriteVerbose(string message) - { - } - - void IOutputWriter.WriteDebug(string message) - { - } - - void IOutputWriter.ThrowTerminatingError(ErrorRecord record) - { - this.consoleHost?.WriteOutput(record.ToString(), true, OutputType.Error, ConsoleColor.Red, ConsoleColor.Black); - } - - #endregion - } -} - -#endif diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Analysis/AnalysisService.cs deleted file mode 100644 index 045a50d31..000000000 --- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs +++ /dev/null @@ -1,708 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Console; -using System.Management.Automation.Runspaces; -using System.Management.Automation; -using System.Collections.Generic; -using System.Text; -using System.Collections; -using System.IO; -using Microsoft.PowerShell.Commands; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides a high-level service for performing semantic analysis - /// of PowerShell scripts. - /// - public class AnalysisService : IDisposable - { - #region Static fields - - /// - /// Defines the list of Script Analyzer rules to include by default if - /// no settings file is specified. - /// - private static readonly string[] s_includedRules = { - "PSUseToExportFieldsInManifest", - "PSMisleadingBacktick", - "PSAvoidUsingCmdletAliases", - "PSUseApprovedVerbs", - "PSAvoidUsingPlainTextForPassword", - "PSReservedCmdletChar", - "PSReservedParams", - "PSShouldProcess", - "PSMissingModuleManifestField", - "PSAvoidDefaultValueSwitchParameter", - "PSUseDeclaredVarsMoreThanAssignments", - "PSPossibleIncorrectComparisonWithNull", - "PSAvoidDefaultValueForMandatoryParameter", - "PSPossibleIncorrectUsageOfRedirectionOperator" - }; - - /// - /// An empty diagnostic result to return when a script fails analysis. - /// - private static readonly PSObject[] s_emptyDiagnosticResult = new PSObject[0]; - - private static readonly string[] s_emptyGetRuleResult = new string[0]; - - /// - /// The indentation to add when the logger lists errors. - /// - private static readonly string s_indentJoin = Environment.NewLine + " "; - - #endregion // Static fields - - #region Private Fields - - /// - /// Maximum number of runspaces we allow to be in use for script analysis. - /// - private const int NumRunspaces = 1; - - /// - /// Name of the PSScriptAnalyzer module, to be used for PowerShell module interactions. - /// - private const string PSSA_MODULE_NAME = "PSScriptAnalyzer"; - - /// - /// Provides logging. - /// - private ILogger _logger; - - /// - /// Runspace pool to generate runspaces for script analysis and handle - /// ansynchronous analysis requests. - /// - private RunspacePool _analysisRunspacePool; - - /// - /// Info object describing the PSScriptAnalyzer module that has been loaded in - /// to provide analysis services. - /// - private PSModuleInfo _pssaModuleInfo; - - #endregion // Private Fields - - #region Properties - - /// - /// Set of PSScriptAnalyzer rules used for analysis. - /// - public string[] ActiveRules { get; set; } - - /// - /// Gets or sets the path to a settings file (.psd1) - /// containing PSScriptAnalyzer settings. - /// - public string SettingsPath { get; set; } - - #endregion - - #region Constructors - - /// - /// Construct a new AnalysisService object. - /// - /// - /// The runspace pool with PSScriptAnalyzer module loaded that will handle - /// analysis tasks. - /// - /// - /// The path to the PSScriptAnalyzer settings file to handle analysis settings. - /// - /// An array of rules to be used for analysis. - /// Maintains logs for the analysis service. - /// - /// Optional module info of the loaded PSScriptAnalyzer module. If not provided, - /// the analysis service will populate it, but it can be given here to save time. - /// - private AnalysisService( - RunspacePool analysisRunspacePool, - string pssaSettingsPath, - IEnumerable activeRules, - ILogger logger, - PSModuleInfo pssaModuleInfo = null) - { - _analysisRunspacePool = analysisRunspacePool; - SettingsPath = pssaSettingsPath; - ActiveRules = activeRules.ToArray(); - _logger = logger; - _pssaModuleInfo = pssaModuleInfo; - } - - #endregion // constructors - - #region Public Methods - - /// - /// Factory method for producing AnalysisService instances. Handles loading of the PSScriptAnalyzer module - /// and runspace pool instantiation before creating the service instance. - /// - /// Path to the PSSA settings file to be used for this service instance. - /// EditorServices logger for logging information. - /// - /// A new analysis service instance with a freshly imported PSScriptAnalyzer module and runspace pool. - /// Returns null if problems occur. This method should never throw. - /// - public static AnalysisService Create(string settingsPath, ILogger logger) - { - try - { - RunspacePool analysisRunspacePool; - PSModuleInfo pssaModuleInfo; - try - { - // Try and load a PSScriptAnalyzer module with the required version - // by looking on the script path. Deep down, this internally runs Get-Module -ListAvailable, - // so we'll use this to check whether such a module exists - analysisRunspacePool = CreatePssaRunspacePool(out pssaModuleInfo); - - } - catch (Exception e) - { - throw new AnalysisServiceLoadException("PSScriptAnalyzer runspace pool could not be created", e); - } - - if (analysisRunspacePool == null) - { - throw new AnalysisServiceLoadException("PSScriptAnalyzer runspace pool failed to be created"); - } - - // Having more than one runspace doesn't block code formatting if one - // runspace is occupied for diagnostics - analysisRunspacePool.SetMaxRunspaces(NumRunspaces); - analysisRunspacePool.ThreadOptions = PSThreadOptions.ReuseThread; - analysisRunspacePool.Open(); - - var analysisService = new AnalysisService( - analysisRunspacePool, - settingsPath, - s_includedRules, - logger, - pssaModuleInfo); - - // Log what features are available in PSSA here - analysisService.LogAvailablePssaFeatures(); - - return analysisService; - } - catch (AnalysisServiceLoadException e) - { - logger.WriteException("PSScriptAnalyzer cannot be imported, AnalysisService will be disabled", e); - return null; - } - catch (Exception e) - { - logger.WriteException("AnalysisService could not be started due to an unexpected exception", e); - return null; - } - } - - /// - /// Get PSScriptAnalyzer settings hashtable for PSProvideCommentHelp rule. - /// - /// Enable the rule. - /// Analyze only exported functions/cmdlets. - /// Use block comment or line comment. - /// Return a vscode snipped correction should be returned. - /// Place comment help at the given location relative to the function definition. - /// A PSScriptAnalyzer settings hashtable. - public static Hashtable GetCommentHelpRuleSettings( - bool enable, - bool exportedOnly, - bool blockComment, - bool vscodeSnippetCorrection, - string placement) - { - var settings = new Dictionary(); - var ruleSettings = new Hashtable(); - ruleSettings.Add("Enable", enable); - ruleSettings.Add("ExportedOnly", exportedOnly); - ruleSettings.Add("BlockComment", blockComment); - ruleSettings.Add("VSCodeSnippetCorrection", vscodeSnippetCorrection); - ruleSettings.Add("Placement", placement); - settings.Add("PSProvideCommentHelp", ruleSettings); - return GetPSSASettingsHashtable(settings); - } - - /// - /// Construct a PSScriptAnalyzer settings hashtable - /// - /// A settings hashtable - /// - public static Hashtable GetPSSASettingsHashtable(IDictionary ruleSettingsMap) - { - var hashtable = new Hashtable(); - var ruleSettingsHashtable = new Hashtable(); - - hashtable["IncludeRules"] = ruleSettingsMap.Keys.ToArray(); - hashtable["Rules"] = ruleSettingsHashtable; - - foreach (var kvp in ruleSettingsMap) - { - ruleSettingsHashtable.Add(kvp.Key, kvp.Value); - } - - return hashtable; - } - - /// - /// Perform semantic analysis on the given ScriptFile and returns - /// an array of ScriptFileMarkers. - /// - /// The ScriptFile which will be analyzed for semantic markers. - /// An array of ScriptFileMarkers containing semantic analysis results. - public async Task> GetSemanticMarkersAsync(ScriptFile file) - { - return await GetSemanticMarkersAsync(file, ActiveRules, SettingsPath); - } - - /// - /// Perform semantic analysis on the given ScriptFile with the given settings. - /// - /// The ScriptFile to be analyzed. - /// ScriptAnalyzer settings - /// - public async Task> GetSemanticMarkersAsync(ScriptFile file, Hashtable settings) - { - return await GetSemanticMarkersAsync(file, null, settings); - } - - /// - /// Perform semantic analysis on the given script with the given settings. - /// - /// The script content to be analyzed. - /// ScriptAnalyzer settings - /// - public async Task> GetSemanticMarkersAsync( - string scriptContent, - Hashtable settings) - { - return await GetSemanticMarkersAsync(scriptContent, null, settings); - } - - /// - /// Returns a list of builtin-in PSScriptAnalyzer rules - /// - public IEnumerable GetPSScriptAnalyzerRules() - { - PowerShellResult getRuleResult = InvokePowerShell("Get-ScriptAnalyzerRule"); - if (getRuleResult == null) - { - _logger.Write(LogLevel.Warning, "Get-ScriptAnalyzerRule returned null result"); - return s_emptyGetRuleResult; - } - - var ruleNames = new List(); - foreach (var rule in getRuleResult.Output) - { - ruleNames.Add((string)rule.Members["RuleName"].Value); - } - - return ruleNames; - } - - /// - /// Format a given script text with default codeformatting settings. - /// - /// Script text to be formatted - /// ScriptAnalyzer settings - /// The range within which formatting should be applied. - /// The formatted script text. - public async Task FormatAsync( - string scriptDefinition, - Hashtable settings, - int[] rangeList) - { - // We cannot use Range type therefore this workaround of using -1 default value. - // Invoke-Formatter throws a ParameterBinderValidationException if the ScriptDefinition is an empty string. - if (string.IsNullOrEmpty(scriptDefinition)) - { - return null; - } - - var argsDict = new Dictionary { - {"ScriptDefinition", scriptDefinition}, - {"Settings", settings} - }; - if (rangeList != null) - { - argsDict.Add("Range", rangeList); - } - - PowerShellResult result = await InvokePowerShellAsync("Invoke-Formatter", argsDict); - - if (result == null) - { - _logger.Write(LogLevel.Error, "Formatter returned null result"); - return null; - } - - if (result.HasErrors) - { - var errorBuilder = new StringBuilder().Append(s_indentJoin); - foreach (ErrorRecord err in result.Errors) - { - errorBuilder.Append(err).Append(s_indentJoin); - } - _logger.Write(LogLevel.Warning, $"Errors found while formatting file: {errorBuilder}"); - return null; - } - - foreach (PSObject resultObj in result.Output) - { - string formatResult = resultObj?.BaseObject as string; - if (formatResult != null) - { - return formatResult; - } - } - - return null; - } - - #endregion // public methods - - #region Private Methods - - private async Task> GetSemanticMarkersAsync( - ScriptFile file, - string[] rules, - TSettings settings) where TSettings : class - { - if (file.IsAnalysisEnabled) - { - return await GetSemanticMarkersAsync( - file.Contents, - rules, - settings); - } - else - { - // Return an empty marker list - return new List(); - } - } - - private async Task> GetSemanticMarkersAsync( - string scriptContent, - string[] rules, - TSettings settings) where TSettings : class - { - if ((typeof(TSettings) == typeof(string) || typeof(TSettings) == typeof(Hashtable)) - && (rules != null || settings != null)) - { - var scriptFileMarkers = await GetDiagnosticRecordsAsync(scriptContent, rules, settings); - return scriptFileMarkers.Select(ScriptFileMarker.FromDiagnosticRecord).ToList(); - } - else - { - // Return an empty marker list - return new List(); - } - } - - /// - /// Log the features available from the PSScriptAnalyzer module that has been imported - /// for use with the AnalysisService. - /// - private void LogAvailablePssaFeatures() - { - // Save ourselves some work here - var featureLogLevel = LogLevel.Verbose; - if (_logger.MinimumConfiguredLogLevel > featureLogLevel) - { - return; - } - - // If we already know the module that was imported, save some work - if (_pssaModuleInfo == null) - { - PowerShellResult getModuleResult = InvokePowerShell( - "Get-Module", - new Dictionary{ {"Name", PSSA_MODULE_NAME} }); - - if (getModuleResult == null) - { - throw new AnalysisServiceLoadException("Get-Module call to find PSScriptAnalyzer module failed"); - } - - _pssaModuleInfo = getModuleResult.Output - .Select(m => m.BaseObject) - .OfType() - .FirstOrDefault(); - } - - if (_pssaModuleInfo == null) - { - throw new AnalysisServiceLoadException("Unable to find loaded PSScriptAnalyzer module for logging"); - } - - var sb = new StringBuilder(); - sb.AppendLine("PSScriptAnalyzer successfully imported:"); - - // Log version - sb.Append(" Version: "); - sb.AppendLine(_pssaModuleInfo.Version.ToString()); - - // Log exported cmdlets - sb.AppendLine(" Exported Cmdlets:"); - foreach (string cmdletName in _pssaModuleInfo.ExportedCmdlets.Keys.OrderBy(name => name)) - { - sb.Append(" "); - sb.AppendLine(cmdletName); - } - - // Log available rules - sb.AppendLine(" Available Rules:"); - foreach (string ruleName in GetPSScriptAnalyzerRules()) - { - sb.Append(" "); - sb.AppendLine(ruleName); - } - - _logger.Write(featureLogLevel, sb.ToString()); - } - - private async Task GetDiagnosticRecordsAsync( - string scriptContent, - string[] rules, - TSettings settings) where TSettings : class - { - var diagnosticRecords = s_emptyDiagnosticResult; - - // When a new, empty file is created there are by definition no issues. - // Furthermore, if you call Invoke-ScriptAnalyzer with an empty ScriptDefinition - // it will generate a ParameterBindingValidationException. - if (string.IsNullOrEmpty(scriptContent)) - { - return diagnosticRecords; - } - - if (typeof(TSettings) == typeof(string) || typeof(TSettings) == typeof(Hashtable)) - { - //Use a settings file if one is provided, otherwise use the default rule list. - string settingParameter; - object settingArgument; - if (settings != null) - { - settingParameter = "Settings"; - settingArgument = settings; - } - else - { - settingParameter = "IncludeRule"; - settingArgument = rules; - } - - PowerShellResult result = await InvokePowerShellAsync( - "Invoke-ScriptAnalyzer", - new Dictionary - { - { "ScriptDefinition", scriptContent }, - { settingParameter, settingArgument }, - // We ignore ParseErrors from PSSA because we already send them when we parse the file. - { "Severity", new [] { ScriptFileMarkerLevel.Error, ScriptFileMarkerLevel.Information, ScriptFileMarkerLevel.Warning }} - }); - - diagnosticRecords = result?.Output; - } - - _logger.Write( - LogLevel.Verbose, - String.Format("Found {0} violations", diagnosticRecords.Count())); - - return diagnosticRecords; - } - - private PowerShellResult InvokePowerShell(string command, IDictionary paramArgMap = null) - { - using (var powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.RunspacePool = _analysisRunspacePool; - powerShell.AddCommand(command); - if (paramArgMap != null) - { - foreach (KeyValuePair kvp in paramArgMap) - { - powerShell.AddParameter(kvp.Key, kvp.Value); - } - } - - PowerShellResult result = null; - try - { - PSObject[] output = powerShell.Invoke().ToArray(); - ErrorRecord[] errors = powerShell.Streams.Error.ToArray(); - result = new PowerShellResult(output, errors, powerShell.HadErrors); - } - catch (CommandNotFoundException ex) - { - // This exception is possible if the module path loaded - // is wrong even though PSScriptAnalyzer is available as a module - _logger.Write(LogLevel.Error, ex.Message); - } - catch (CmdletInvocationException ex) - { - // We do not want to crash EditorServices for exceptions caused by cmdlet invocation. - // Two main reasons that cause the exception are: - // * PSCmdlet.WriteOutput being called from another thread than Begin/Process - // * CompositionContainer.ComposeParts complaining that "...Only one batch can be composed at a time" - _logger.Write(LogLevel.Error, ex.Message); - } - - return result; - } - } - - private async Task InvokePowerShellAsync(string command, IDictionary paramArgMap = null) - { - var task = Task.Run(() => - { - return InvokePowerShell(command, paramArgMap); - }); - - return await task; - } - - /// - /// Create a new runspace pool around a PSScriptAnalyzer module for asynchronous script analysis tasks. - /// This looks for the latest version of PSScriptAnalyzer on the path and loads that. - /// - /// A runspace pool with PSScriptAnalyzer loaded for running script analysis tasks. - private static RunspacePool CreatePssaRunspacePool(out PSModuleInfo pssaModuleInfo) - { - using (var ps = System.Management.Automation.PowerShell.Create()) - { - // Run `Get-Module -ListAvailable -Name "PSScriptAnalyzer"` - ps.AddCommand("Get-Module") - .AddParameter("ListAvailable") - .AddParameter("Name", PSSA_MODULE_NAME); - - try - { - // Get the latest version of PSScriptAnalyzer we can find - pssaModuleInfo = ps.Invoke()? - .Select(psObj => psObj.BaseObject) - .OfType() - .OrderByDescending(moduleInfo => moduleInfo.Version) - .FirstOrDefault(); - } - catch (Exception e) - { - throw new AnalysisServiceLoadException("Unable to find PSScriptAnalyzer module on the module path", e); - } - - if (pssaModuleInfo == null) - { - throw new AnalysisServiceLoadException("Unable to find PSScriptAnalyzer module on the module path"); - } - - // Create a base session state with PSScriptAnalyzer loaded - InitialSessionState sessionState; - if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") { - sessionState = InitialSessionState.CreateDefault(); - } else { - sessionState = InitialSessionState.CreateDefault2(); - } - sessionState.ImportPSModule(new [] { pssaModuleInfo.ModuleBase }); - - // RunspacePool takes care of queuing commands for us so we do not - // need to worry about executing concurrent commands - return RunspaceFactory.CreateRunspacePool(sessionState); - } - } - - #endregion //private methods - - #region IDisposable Support - - private bool _disposedValue = false; // To detect redundant calls - - /// - /// Dispose of this object. - /// - /// True if the method is called by the Dispose method, false if called by the finalizer. - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - _analysisRunspacePool.Dispose(); - _analysisRunspacePool = null; - } - - _disposedValue = true; - } - } - - /// - /// Clean up all internal resources and dispose of the analysis service. - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - } - - #endregion - - /// - /// Wraps the result of an execution of PowerShell to send back through - /// asynchronous calls. - /// - private class PowerShellResult - { - public PowerShellResult( - PSObject[] output, - ErrorRecord[] errors, - bool hasErrors) - { - Output = output; - Errors = errors; - HasErrors = hasErrors; - } - - public PSObject[] Output { get; } - - public ErrorRecord[] Errors { get; } - - public bool HasErrors { get; } - } - } - - /// - /// Class to catch known failure modes for starting the AnalysisService. - /// - public class AnalysisServiceLoadException : Exception - { - /// - /// Instantiate an AnalysisService error based on a simple message. - /// - /// The message to display to the user detailing the error. - public AnalysisServiceLoadException(string message) - : base(message) - { - } - - /// - /// Instantiate an AnalysisService error based on another error that occurred internally. - /// - /// The message to display to the user detailing the error. - /// The inner exception that occurred to trigger this error. - public AnalysisServiceLoadException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/src/PowerShellEditorServices/CodeLenses/CodeLens.cs b/src/PowerShellEditorServices/CodeLenses/CodeLens.cs deleted file mode 100644 index 2fd251595..000000000 --- a/src/PowerShellEditorServices/CodeLenses/CodeLens.cs +++ /dev/null @@ -1,127 +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 Microsoft.PowerShell.EditorServices.Commands; -using Microsoft.PowerShell.EditorServices.Utility; -using System.Collections.Generic; -using System.Management.Automation.Language; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.CodeLenses -{ - /// - /// Defines the data for a "code lens" which is displayed - /// above a symbol in a text document and has an associated - /// command. - /// - public class CodeLens - { - /// - /// Gets the ICodeLensProvider that created this CodeLens. - /// - public ICodeLensProvider Provider { get; private set; } - - /// - /// Gets the ScriptFile for which the CodeLens was created. - /// - public ScriptFile File { get; private set; } - - /// - /// Gets the IScriptExtent for the region which the CodeLens - /// pertains. - /// - public IScriptExtent ScriptExtent { get; private set; } - - /// - /// Gets the command which will be invoked in the editor - /// when the CodeLens is clicked. - /// - public ClientCommand Command { get; private set; } - - /// - /// Creates an instance of the CodeLens class. - /// - /// - /// The ICodeLensProvider which created this CodeLens. - /// - /// - /// The ScriptFile for which the CodeLens was created. - /// - /// - /// The IScriptExtent for the region which the CodeLens - /// pertains. - /// - public CodeLens( - ICodeLensProvider provider, - ScriptFile scriptFile, - IScriptExtent scriptExtent) - : this( - provider, - scriptFile, - scriptExtent, - null) - { - } - - /// - /// Creates an instance of the CodeLens class based on an - /// original CodeLens instance, generally used when resolving - /// the Command for a CodeLens. - /// - /// - /// The original CodeLens upon which this instance is based. - /// - /// - /// The resolved ClientCommand for the original CodeLens. - /// - public CodeLens( - CodeLens originalCodeLens, - ClientCommand resolvedCommand) - { - Validate.IsNotNull(nameof(originalCodeLens), originalCodeLens); - Validate.IsNotNull(nameof(resolvedCommand), resolvedCommand); - - this.Provider = originalCodeLens.Provider; - this.File = originalCodeLens.File; - this.ScriptExtent = originalCodeLens.ScriptExtent; - this.Command = resolvedCommand; - } - - /// - /// Creates an instance of the CodeLens class. - /// - /// - /// The ICodeLensProvider which created this CodeLens. - /// - /// - /// The ScriptFile for which the CodeLens was created. - /// - /// - /// The IScriptExtent for the region which the CodeLens - /// pertains. - /// - /// - /// The ClientCommand to execute when this CodeLens is clicked. - /// If null, this CodeLens will be resolved by the editor when it - /// gets displayed. - /// - public CodeLens( - ICodeLensProvider provider, - ScriptFile scriptFile, - IScriptExtent scriptExtent, - ClientCommand command) - { - Validate.IsNotNull(nameof(provider), provider); - Validate.IsNotNull(nameof(scriptFile), scriptFile); - Validate.IsNotNull(nameof(scriptExtent), scriptExtent); - - this.Provider = provider; - this.File = scriptFile; - this.ScriptExtent = scriptExtent; - this.Command = command; - } - } -} diff --git a/src/PowerShellEditorServices/CodeLenses/ICodeLensProvider.cs b/src/PowerShellEditorServices/CodeLenses/ICodeLensProvider.cs deleted file mode 100644 index 71a1c0243..000000000 --- a/src/PowerShellEditorServices/CodeLenses/ICodeLensProvider.cs +++ /dev/null @@ -1,46 +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 Microsoft.PowerShell.EditorServices.Utility; -using System.Collections.Generic; -using System.Management.Automation.Language; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.CodeLenses -{ - /// - /// Specifies the contract for a Code Lens provider. - /// - public interface ICodeLensProvider : IFeatureProvider - { - /// - /// Provides a collection of CodeLenses for the given - /// document. - /// - /// - /// The document for which CodeLenses should be provided. - /// - /// An array of CodeLenses. - CodeLens[] ProvideCodeLenses(ScriptFile scriptFile); - - /// - /// Resolves a CodeLens that was created without a Command. - /// - /// - /// The CodeLens to resolve. - /// - /// - /// A CancellationToken which can be used to cancel the - /// request. - /// - /// - /// A Task which returns the resolved CodeLens when completed. - /// - Task ResolveCodeLensAsync( - CodeLens codeLens, - CancellationToken cancellationToken); - } -} diff --git a/src/PowerShellEditorServices/CodeLenses/ICodeLenses.cs b/src/PowerShellEditorServices/CodeLenses/ICodeLenses.cs deleted file mode 100644 index f63319c1c..000000000 --- a/src/PowerShellEditorServices/CodeLenses/ICodeLenses.cs +++ /dev/null @@ -1,34 +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.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.CodeLenses -{ - /// - /// Specifies the contract for an implementation of - /// the ICodeLenses component. - /// - public interface ICodeLenses - { - /// - /// Gets the collection of ICodeLensProvider implementations - /// that are registered with this component. - /// - IFeatureProviderCollection Providers { get; } - - /// - /// Provides a collection of CodeLenses for the given - /// document. - /// - /// - /// The document for which CodeLenses should be provided. - /// - /// An array of CodeLenses. - CodeLens[] ProvideCodeLenses(ScriptFile scriptFile); - } -} diff --git a/src/PowerShellEditorServices/Commands/ClientCommand.cs b/src/PowerShellEditorServices/Commands/ClientCommand.cs deleted file mode 100644 index 42ee9a147..000000000 --- a/src/PowerShellEditorServices/Commands/ClientCommand.cs +++ /dev/null @@ -1,46 +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.Commands -{ - /// - /// Provides details for a command which will be executed - /// in the host editor. - /// - public class ClientCommand - { - /// - /// Gets the identifying name of the command. - /// - public string Name { get; private set; } - - /// - /// Gets the display title of the command. - /// - public string Title { get; private set; } - - /// - /// Gets the array of objects which are passed as - /// arguments to the command. - /// - public object[] Arguments { get; private set; } - - /// - /// Creates an instance of the ClientCommand class. - /// - /// The name of the command. - /// The display title of the command. - /// The arguments to be passed to the command. - public ClientCommand( - string commandName, - string commandTitle, - object[] arguments) - { - this.Name = commandName; - this.Title = commandTitle; - this.Arguments = arguments; - } - } -} diff --git a/src/PowerShellEditorServices/Components/ComponentRegistry.cs b/src/PowerShellEditorServices/Components/ComponentRegistry.cs deleted file mode 100644 index 9a1de6d01..000000000 --- a/src/PowerShellEditorServices/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/Components/FeatureComponentBase.cs b/src/PowerShellEditorServices/Components/FeatureComponentBase.cs deleted file mode 100644 index b2eac7539..000000000 --- a/src/PowerShellEditorServices/Components/FeatureComponentBase.cs +++ /dev/null @@ -1,127 +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.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Components -{ - /// - /// Provides common functionality needed to implement a feature - /// component which uses IFeatureProviders to provide further - /// extensibility. - /// - public abstract class FeatureComponentBase - where TProvider : IFeatureProvider - { - /// - /// Gets the collection of IFeatureProviders registered with - /// this feature component. - /// - public IFeatureProviderCollection Providers { get; private set; } - - /// - /// Gets the ILogger implementation to use for writing log - /// messages. - /// - protected ILogger Logger { get; private set; } - - /// - /// Creates an instance of the FeatureComponentBase class with - /// the specified ILogger. - /// - /// The ILogger implementation to use for this instance. - public FeatureComponentBase(ILogger logger) - { - this.Providers = new FeatureProviderCollection(); - this.Logger = logger; - } - - /// - /// Invokes the given function synchronously against all - /// registered providers. - /// - /// The function to be invoked. - /// - /// An IEnumerable containing the results of all providers - /// that were invoked successfully. - /// - protected IEnumerable InvokeProviders( - Func invokeFunc) - { - Stopwatch invokeTimer = new Stopwatch(); - List providerResults = new List(); - - foreach (var provider in this.Providers) - { - try - { - invokeTimer.Restart(); - - providerResults.Add(invokeFunc(provider)); - - invokeTimer.Stop(); - - this.Logger.Write( - LogLevel.Verbose, - $"Invocation of provider '{provider.ProviderId}' completed in {invokeTimer.ElapsedMilliseconds}ms."); - } - catch (Exception e) - { - this.Logger.WriteException( - $"Exception caught while invoking provider {provider.ProviderId}:", - e); - } - } - - return providerResults; - } - - /// - /// Invokes the given function asynchronously against all - /// registered providers. - /// - /// The function to be invoked. - /// - /// A Task that, when completed, returns an IEnumerable containing - /// the results of all providers that were invoked successfully. - /// - protected async Task> InvokeProvidersAsync( - Func> invokeFunc) - { - Stopwatch invokeTimer = new Stopwatch(); - List providerResults = new List(); - - foreach (var provider in this.Providers) - { - try - { - invokeTimer.Restart(); - - providerResults.Add( - await invokeFunc(provider)); - - invokeTimer.Stop(); - - this.Logger.Write( - LogLevel.Verbose, - $"Invocation of provider '{provider.ProviderId}' completed in {invokeTimer.ElapsedMilliseconds}ms."); - } - catch (Exception e) - { - this.Logger.WriteException( - $"Exception caught while invoking provider {provider.ProviderId}:", - e); - } - } - - return providerResults; - } - } -} diff --git a/src/PowerShellEditorServices/Components/IComponentRegistry.cs b/src/PowerShellEditorServices/Components/IComponentRegistry.cs deleted file mode 100644 index 1acd59588..000000000 --- a/src/PowerShellEditorServices/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); - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Components/IComponentRegistryExtensions.cs b/src/PowerShellEditorServices/Components/IComponentRegistryExtensions.cs deleted file mode 100644 index cbbb119c1..000000000 --- a/src/PowerShellEditorServices/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; - } - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Console/ChoiceDetails.cs b/src/PowerShellEditorServices/Console/ChoiceDetails.cs deleted file mode 100644 index d8121b6c1..000000000 --- a/src/PowerShellEditorServices/Console/ChoiceDetails.cs +++ /dev/null @@ -1,132 +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.Management.Automation.Host; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Contains the details about a choice that should be displayed - /// to the user. This class is meant to be serializable to the - /// user's UI. - /// - public class ChoiceDetails - { - #region Private Fields - - private string hotKeyString; - - #endregion - - #region Properties - - /// - /// Gets the label for the choice. - /// - public string Label { get; set; } - - /// - /// Gets the index of the hot key character for the choice. - /// - public int HotKeyIndex { get; set; } - - /// - /// Gets the hot key character. - /// - public char? HotKeyCharacter { get; set; } - - /// - /// Gets the help string that describes the choice. - /// - public string HelpMessage { get; set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ChoiceDetails class with - /// the provided details. - /// - public ChoiceDetails() - { - // Parameterless constructor for deserialization. - } - - /// - /// Creates an instance of the ChoiceDetails class with - /// the provided details. - /// - /// - /// The label of the choice. An ampersand '&' may be inserted - /// before the character that will used as a hot key for the - /// choice. - /// - /// - /// A help message that describes the purpose of the choice. - /// - public ChoiceDetails(string label, string helpMessage) - { - this.HelpMessage = helpMessage; - - this.HotKeyIndex = label.IndexOf('&'); - if (this.HotKeyIndex >= 0) - { - this.Label = label.Remove(this.HotKeyIndex, 1); - - if (this.HotKeyIndex < this.Label.Length) - { - this.hotKeyString = this.Label[this.HotKeyIndex].ToString().ToUpper(); - this.HotKeyCharacter = this.hotKeyString[0]; - } - } - else - { - this.Label = label; - } - } - - /// - /// Creates a new instance of the ChoicePromptDetails class - /// based on a ChoiceDescription from the PowerShell layer. - /// - /// - /// A ChoiceDescription on which this instance will be based. - /// - /// A new ChoicePromptDetails instance. - public static ChoiceDetails Create(ChoiceDescription choiceDescription) - { - return new ChoiceDetails( - choiceDescription.Label, - choiceDescription.HelpMessage); - } - - #endregion - - #region Public Methods - - /// - /// Compares an input string to this choice to determine - /// whether the input string is a match. - /// - /// - /// The input string to compare to the choice. - /// - /// True if the input string is a match for the choice. - public bool MatchesInput(string inputString) - { - // Make sure the input string is trimmed of whitespace - inputString = inputString.Trim(); - - // Is it the hotkey? - return - string.Equals(inputString, this.hotKeyString, StringComparison.CurrentCultureIgnoreCase) || - string.Equals(inputString, this.Label, StringComparison.CurrentCultureIgnoreCase); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs b/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs deleted file mode 100644 index 579d5b708..000000000 --- a/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs +++ /dev/null @@ -1,354 +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.Linq; -using System.Management.Automation; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Indicates the style of prompt to be displayed. - /// - public enum PromptStyle - { - /// - /// Indicates that the full prompt should be displayed - /// with all relevant details. - /// - Full, - - /// - /// Indicates that a minimal prompt should be displayed, - /// generally used after the full prompt has already been - /// displayed and the options must be displayed again. - /// - Minimal - } - - /// - /// Provides a base implementation for IPromptHandler classes - /// that present the user a set of options from which a selection - /// should be made. - /// - public abstract class ChoicePromptHandler : PromptHandler - { - #region Private Fields - - private CancellationTokenSource promptCancellationTokenSource = - new CancellationTokenSource(); - private TaskCompletionSource> cancelTask = - new TaskCompletionSource>(); - - #endregion - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public ChoicePromptHandler(ILogger logger) : base(logger) - { - } - - #region Properties - - /// - /// Returns true if the choice prompt allows multiple selections. - /// - protected bool IsMultiChoice { get; private set; } - - /// - /// Gets the caption (title) string to display with the prompt. - /// - protected string Caption { get; private set; } - - /// - /// Gets the descriptive message to display with the prompt. - /// - protected string Message { get; private set; } - - /// - /// Gets the array of choices from which the user must select. - /// - protected ChoiceDetails[] Choices { get; private set; } - - /// - /// Gets the index of the default choice so that the user - /// interface can make it easy to select this option. - /// - protected int[] DefaultChoices { get; private set; } - - #endregion - - #region Public Methods - - /// - /// Prompts the user to make a choice using the provided details. - /// - /// - /// The caption string which will be displayed to the user. - /// - /// - /// The descriptive message which will be displayed to the user. - /// - /// - /// The list of choices from which the user will select. - /// - /// - /// The default choice to highlight for the user. - /// - /// - /// A CancellationToken that can be used to cancel the prompt. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's choice. - /// - public async Task PromptForChoiceAsync( - string promptCaption, - string promptMessage, - ChoiceDetails[] choices, - int defaultChoice, - CancellationToken cancellationToken) - { - // TODO: Guard against multiple calls - - this.Caption = promptCaption; - this.Message = promptMessage; - this.Choices = choices; - - this.DefaultChoices = - defaultChoice == -1 - ? new int[] { } - : new int[] { defaultChoice }; - - // Cancel the TaskCompletionSource if the caller cancels the task - cancellationToken.Register(this.CancelPrompt, true); - - // Convert the int[] result to int - return await this.WaitForTaskAsync( - this.StartPromptLoopAsync(this.promptCancellationTokenSource.Token) - .ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - return this.GetSingleResult(task.Result); - })); - } - - /// - /// Prompts the user to make a choice of one or more options using the - /// provided details. - /// - /// - /// The caption string which will be displayed to the user. - /// - /// - /// The descriptive message which will be displayed to the user. - /// - /// - /// The list of choices from which the user will select. - /// - /// - /// The default choice(s) to highlight for the user. - /// - /// - /// A CancellationToken that can be used to cancel the prompt. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's choices. - /// - public async Task PromptForChoiceAsync( - string promptCaption, - string promptMessage, - ChoiceDetails[] choices, - int[] defaultChoices, - CancellationToken cancellationToken) - { - // TODO: Guard against multiple calls - - this.Caption = promptCaption; - this.Message = promptMessage; - this.Choices = choices; - this.DefaultChoices = defaultChoices; - this.IsMultiChoice = true; - - // Cancel the TaskCompletionSource if the caller cancels the task - cancellationToken.Register(this.CancelPrompt, true); - - return await this.WaitForTaskAsync( - this.StartPromptLoopAsync( - this.promptCancellationTokenSource.Token)); - } - - private async Task WaitForTaskAsync(Task taskToWait) - { - Task finishedTask = - await Task.WhenAny( - this.cancelTask.Task, - taskToWait); - - if (this.cancelTask.Task.IsCanceled) - { - throw new PipelineStoppedException(); - } - - return taskToWait.Result; - } - - private async Task StartPromptLoopAsync( - CancellationToken cancellationToken) - { - int[] choiceIndexes = null; - - // Show the prompt to the user - this.ShowPrompt(PromptStyle.Full); - - while (!cancellationToken.IsCancellationRequested) - { - string responseString = await this.ReadInputStringAsync(cancellationToken); - if (responseString == null) - { - // If the response string is null, the prompt has been cancelled - break; - } - - choiceIndexes = this.HandleResponse(responseString); - - // Return the default choice values if no choices were entered - if (choiceIndexes == null && string.IsNullOrEmpty(responseString)) - { - choiceIndexes = this.DefaultChoices; - } - - // If the user provided no choices, we should prompt again - if (choiceIndexes != null) - { - break; - } - - // The user did not respond with a valid choice, - // show the prompt again to give another chance - this.ShowPrompt(PromptStyle.Minimal); - } - - if (cancellationToken.IsCancellationRequested) - { - // Throw a TaskCanceledException to stop the pipeline - throw new TaskCanceledException(); - } - - return choiceIndexes?.ToArray(); - } - - /// - /// Implements behavior to handle the user's response. - /// - /// The string representing the user's response. - /// - /// True if the prompt is complete, false if the prompt is - /// still waiting for a valid response. - /// - protected virtual int[] HandleResponse(string responseString) - { - List choiceIndexes = new List(); - - // Clean up the response string and split it - var choiceStrings = - responseString.Trim().Split( - new char[] { ',' }, - StringSplitOptions.RemoveEmptyEntries); - - foreach (string choiceString in choiceStrings) - { - for (int i = 0; i < this.Choices.Length; i++) - { - if (this.Choices[i].MatchesInput(choiceString)) - { - choiceIndexes.Add(i); - - // If this is a single-choice prompt, break out after - // the first matched choice - if (!this.IsMultiChoice) - { - break; - } - } - } - } - - if (choiceIndexes.Count == 0) - { - // The user did not respond with a valid choice, - // show the prompt again to give another chance - return null; - } - - return choiceIndexes.ToArray(); - } - - /// - /// Called when the active prompt should be cancelled. - /// - protected override void OnPromptCancelled() - { - // Cancel the prompt task - this.promptCancellationTokenSource.Cancel(); - this.cancelTask.TrySetCanceled(); - } - - #endregion - - #region Abstract Methods - - /// - /// Called when the prompt should be displayed to the user. - /// - /// - /// Indicates the prompt style to use when showing the prompt. - /// - protected abstract void ShowPrompt(PromptStyle promptStyle); - - /// - /// Reads an input string asynchronously from the console. - /// - /// - /// A CancellationToken that can be used to cancel the read. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - protected abstract Task ReadInputStringAsync(CancellationToken cancellationToken); - - #endregion - - #region Private Methods - - private int GetSingleResult(int[] choiceArray) - { - return - choiceArray != null - ? choiceArray.DefaultIfEmpty(-1).First() - : -1; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Console/CollectionFieldDetails.cs b/src/PowerShellEditorServices/Console/CollectionFieldDetails.cs deleted file mode 100644 index 80ae62b5f..000000000 --- a/src/PowerShellEditorServices/Console/CollectionFieldDetails.cs +++ /dev/null @@ -1,138 +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; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Contains the details of an colleciton input field shown - /// from an InputPromptHandler. This class is meant to be - /// serializable to the user's UI. - /// - public class CollectionFieldDetails : FieldDetails - { - #region Private Fields - - private bool isArray; - private bool isEntryComplete; - private string fieldName; - private int currentCollectionIndex; - private ArrayList collectionItems = new ArrayList(); - - #endregion - - #region Constructors - - /// - /// Creates an instance of the CollectionFieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The field's value type. - /// If true, marks the field as mandatory. - /// The field's default value. - public CollectionFieldDetails( - string name, - string label, - Type fieldType, - bool isMandatory, - object defaultValue) - : base(name, label, fieldType, isMandatory, defaultValue) - { - this.fieldName = name; - - this.FieldType = typeof(object); - - if (fieldType.IsArray) - { - this.isArray = true; - this.FieldType = fieldType.GetElementType(); - } - - this.Name = - string.Format( - "{0}[{1}]", - this.fieldName, - this.currentCollectionIndex); - } - - #endregion - - #region Public Methods - - /// - /// Gets the next field to display if this is a complex - /// field, otherwise returns null. - /// - /// - /// A FieldDetails object if there's another field to - /// display or if this field is complete. - /// - public override FieldDetails GetNextField() - { - if (!this.isEntryComplete) - { - // Get the next collection field - this.currentCollectionIndex++; - this.Name = - string.Format( - "{0}[{1}]", - this.fieldName, - this.currentCollectionIndex); - - return this; - } - else - { - return null; - } - } - - /// - /// Sets the field's value. - /// - /// The field's value. - /// - /// True if a value has been supplied by the user, false if the user supplied no value. - /// - public override void SetValue(object fieldValue, bool hasValue) - { - if (hasValue) - { - // Add the item to the collection - this.collectionItems.Add(fieldValue); - } - else - { - this.isEntryComplete = true; - } - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - protected override object OnGetValue() - { - object collection = this.collectionItems; - - // Should the result collection be an array? - if (this.isArray) - { - // Convert the ArrayList to an array - collection = - this.collectionItems.ToArray( - this.FieldType); - } - - return collection; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs b/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs deleted file mode 100644 index 74b51c876..000000000 --- a/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs +++ /dev/null @@ -1,135 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides a standard implementation of ChoicePromptHandler - /// for use in the interactive console (REPL). - /// - public abstract class ConsoleChoicePromptHandler : ChoicePromptHandler - { - #region Private Fields - - /// - /// The IHostOutput instance to use for this prompt. - /// - protected IHostOutput hostOutput; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleChoicePromptHandler class. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public ConsoleChoicePromptHandler( - IHostOutput hostOutput, - ILogger logger) - : base(logger) - { - this.hostOutput = hostOutput; - } - - #endregion - - /// - /// Called when the prompt should be displayed to the user. - /// - /// - /// Indicates the prompt style to use when showing the prompt. - /// - protected override void ShowPrompt(PromptStyle promptStyle) - { - if (promptStyle == PromptStyle.Full) - { - if (this.Caption != null) - { - this.hostOutput.WriteOutput(this.Caption); - } - - if (this.Message != null) - { - this.hostOutput.WriteOutput(this.Message); - } - } - - foreach (var choice in this.Choices) - { - string hotKeyString = - choice.HotKeyIndex > -1 ? - choice.Label[choice.HotKeyIndex].ToString().ToUpper() : - string.Empty; - - this.hostOutput.WriteOutput( - string.Format( - "[{0}] {1} ", - hotKeyString, - choice.Label), - false); - } - - this.hostOutput.WriteOutput("[?] Help", false); - - var validDefaultChoices = - this.DefaultChoices.Where( - choice => choice > -1 && choice < this.Choices.Length); - - if (validDefaultChoices.Any()) - { - var choiceString = - string.Join( - ", ", - this.DefaultChoices - .Select(choice => this.Choices[choice].Label)); - - this.hostOutput.WriteOutput( - $" (default is \"{choiceString}\"): ", - false); - } - } - - - /// - /// Implements behavior to handle the user's response. - /// - /// The string representing the user's response. - /// - /// True if the prompt is complete, false if the prompt is - /// still waiting for a valid response. - /// - protected override int[] HandleResponse(string responseString) - { - if (responseString.Trim() == "?") - { - // Print help text - foreach (var choice in this.Choices) - { - this.hostOutput.WriteOutput( - string.Format( - "{0} - {1}", - (choice.HotKeyCharacter.HasValue ? - choice.HotKeyCharacter.Value.ToString() : - choice.Label), - choice.HelpMessage)); - } - - return null; - } - - return base.HandleResponse(responseString); - } - } -} diff --git a/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs b/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs deleted file mode 100644 index 8124633a7..000000000 --- a/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs +++ /dev/null @@ -1,105 +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.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides a standard implementation of InputPromptHandler - /// for use in the interactive console (REPL). - /// - public abstract class ConsoleInputPromptHandler : InputPromptHandler - { - #region Private Fields - - /// - /// The IHostOutput instance to use for this prompt. - /// - protected IHostOutput hostOutput; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleInputPromptHandler class. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public ConsoleInputPromptHandler( - IHostOutput hostOutput, - ILogger logger) - : base(logger) - { - this.hostOutput = hostOutput; - } - - #endregion - - #region Public Methods - - /// - /// Called when the prompt caption and message should be - /// displayed to the user. - /// - /// The caption string to be displayed. - /// The message string to be displayed. - protected override void ShowPromptMessage(string caption, string message) - { - if (!string.IsNullOrEmpty(caption)) - { - this.hostOutput.WriteOutput(caption, true); - } - - if (!string.IsNullOrEmpty(message)) - { - this.hostOutput.WriteOutput(message, true); - } - } - - /// - /// Called when a prompt should be displayed for a specific - /// input field. - /// - /// The details of the field to be displayed. - protected override void ShowFieldPrompt(FieldDetails fieldDetails) - { - // For a simple prompt there won't be any field name. - // In this case don't write anything - if (!string.IsNullOrEmpty(fieldDetails.Name)) - { - this.hostOutput.WriteOutput( - fieldDetails.Name + ": ", - false); - } - } - - /// - /// Called when an error should be displayed, such as when the - /// user types in a string with an incorrect format for the - /// current field. - /// - /// - /// The Exception containing the error to be displayed. - /// - protected override void ShowErrorMessage(Exception e) - { - this.hostOutput.WriteOutput( - e.Message, - true, - OutputType.Error); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/ConsoleProxy.cs b/src/PowerShellEditorServices/Console/ConsoleProxy.cs deleted file mode 100644 index b9312ca7c..000000000 --- a/src/PowerShellEditorServices/Console/ConsoleProxy.cs +++ /dev/null @@ -1,196 +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.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides asynchronous implementations of the API's as well as - /// synchronous implementations that work around platform specific issues. - /// - internal static class ConsoleProxy - { - private static IConsoleOperations s_consoleProxy; - - static ConsoleProxy() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - s_consoleProxy = new WindowsConsoleOperations(); - return; - } - - s_consoleProxy = new UnixConsoleOperations(); - } - - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// An object that describes the constant and Unicode character, if any, - /// that correspond to the pressed console key. The object also - /// describes, in a bitwise combination of values, whether - /// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key. - /// - public static ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) => - s_consoleProxy.ReadKey(intercept, cancellationToken); - - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// A task that will complete with a result of the key pressed by the user. - /// - public static Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) => - s_consoleProxy.ReadKeyAsync(intercept, cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The horizontal position of the console cursor. - public static int GetCursorLeft() => - s_consoleProxy.GetCursorLeft(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The horizontal position of the console cursor. - public static int GetCursorLeft(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorLeft(cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - public static Task GetCursorLeftAsync() => - s_consoleProxy.GetCursorLeftAsync(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - public static Task GetCursorLeftAsync(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorLeftAsync(cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The vertical position of the console cursor. - public static int GetCursorTop() => - s_consoleProxy.GetCursorTop(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The vertical position of the console cursor. - public static int GetCursorTop(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorTop(cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - public static Task GetCursorTopAsync() => - s_consoleProxy.GetCursorTopAsync(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - public static Task GetCursorTopAsync(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorTopAsync(cancellationToken); - - /// - /// On Unix platforms this method is sent to PSReadLine as a work around for issues - /// with the System.Console implementation for that platform. Functionally it is the - /// same as System.Console.ReadKey, with the exception that it will not lock the - /// standard input stream. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// true to not display the pressed key; otherwise, false. - /// - /// - /// The that can be used to cancel the request. - /// - /// - /// An object that describes the ConsoleKey constant and Unicode character, if any, - /// that correspond to the pressed console key. The ConsoleKeyInfo object also describes, - /// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt, - /// or Ctrl modifier keys was pressed simultaneously with the console key. - /// - internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken cancellationToken) - { - try - { - return ((UnixConsoleOperations)s_consoleProxy).ReadKey(intercept, cancellationToken); - } - catch (OperationCanceledException) - { - return default(ConsoleKeyInfo); - } - } - } -} diff --git a/src/PowerShellEditorServices/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Console/ConsoleReadLine.cs deleted file mode 100644 index af6ca044c..000000000 --- a/src/PowerShellEditorServices/Console/ConsoleReadLine.cs +++ /dev/null @@ -1,616 +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.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - using System; - using System.Management.Automation; - using System.Management.Automation.Language; - using System.Security; - - internal class ConsoleReadLine - { - #region Private Field - private PowerShellContext powerShellContext; - - #endregion - - #region Constructors - - public ConsoleReadLine(PowerShellContext powerShellContext) - { - this.powerShellContext = powerShellContext; - } - - #endregion - - #region Public Methods - - public Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return this.ReadLineAsync(true, cancellationToken); - } - - public Task ReadSimpleLineAsync(CancellationToken cancellationToken) - { - return this.ReadLineAsync(false, cancellationToken); - } - - public async Task ReadSecureLineAsync(CancellationToken cancellationToken) - { - SecureString secureString = new SecureString(); - - int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken); - int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken); - int previousInputLength = 0; - - Console.TreatControlCAsInput = true; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken); - - if ((int)keyInfo.Key == 3 || - keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - throw new PipelineStoppedException(); - } - if (keyInfo.Key == ConsoleKey.Enter) - { - // Break to return the completed string - break; - } - if (keyInfo.Key == ConsoleKey.Tab) - { - continue; - } - if (keyInfo.Key == ConsoleKey.Backspace) - { - if (secureString.Length > 0) - { - secureString.RemoveAt(secureString.Length - 1); - } - } - else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) - { - secureString.AppendChar(keyInfo.KeyChar); - } - - // Re-render the secure string characters - int currentInputLength = secureString.Length; - int consoleWidth = Console.WindowWidth; - - if (currentInputLength > previousInputLength) - { - Console.Write('*'); - } - else if (previousInputLength > 0 && currentInputLength < previousInputLength) - { - int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken); - int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken); - - // Back up the cursor before clearing the character - col--; - if (col < 0) - { - col = consoleWidth - 1; - row--; - } - - Console.SetCursorPosition(col, row); - Console.Write(' '); - Console.SetCursorPosition(col, row); - } - - previousInputLength = currentInputLength; - } - } - finally - { - Console.TreatControlCAsInput = false; - } - - return secureString; - } - - #endregion - - #region Private Methods - - private static async Task ReadKeyAsync(CancellationToken cancellationToken) - { - return await ConsoleProxy.ReadKeyAsync(intercept: true, cancellationToken); - } - - private async Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - return await this.powerShellContext.InvokeReadLineAsync(isCommandLine, cancellationToken); - } - - /// - /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. - /// This method should be used when PSReadLine is disabled, either by user settings or - /// unsupported PowerShell versions. - /// - /// - /// Indicates whether ReadLine should act like a command line. - /// - /// - /// The cancellation token that will be checked prior to completing the returned task. - /// - /// - /// A task object representing the asynchronus operation. The Result property on - /// the task object returns the user input string. - /// - internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - string inputBeforeCompletion = null; - string inputAfterCompletion = null; - CommandCompletion currentCompletion = null; - - int historyIndex = -1; - Collection currentHistory = null; - - StringBuilder inputLine = new StringBuilder(); - - int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken); - int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken); - - int initialWindowLeft = Console.WindowLeft; - int initialWindowTop = Console.WindowTop; - - int currentCursorIndex = 0; - - Console.TreatControlCAsInput = true; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken); - - // Do final position calculation after the key has been pressed - // because the window could have been resized before then - int promptStartCol = initialCursorCol; - int promptStartRow = initialCursorRow; - int consoleWidth = Console.WindowWidth; - - if ((int)keyInfo.Key == 3 || - keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - throw new PipelineStoppedException(); - } - else if (keyInfo.Key == ConsoleKey.Tab && isCommandLine) - { - if (currentCompletion == null) - { - inputBeforeCompletion = inputLine.ToString(); - inputAfterCompletion = null; - - // TODO: This logic should be moved to AstOperations or similar! - - if (this.powerShellContext.IsDebuggerStopped) - { - PSCommand command = new PSCommand(); - command.AddCommand("TabExpansion2"); - command.AddParameter("InputScript", inputBeforeCompletion); - command.AddParameter("CursorColumn", currentCursorIndex); - command.AddParameter("Options", null); - - var results = - await this.powerShellContext.ExecuteCommandAsync(command, false, false); - - currentCompletion = results.FirstOrDefault(); - } - else - { - using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandleAsync()) - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspaceHandle.Runspace; - currentCompletion = - CommandCompletion.CompleteInput( - inputBeforeCompletion, - currentCursorIndex, - null, - powerShell); - - if (currentCompletion.CompletionMatches.Count > 0) - { - int replacementEndIndex = - currentCompletion.ReplacementIndex + - currentCompletion.ReplacementLength; - - inputAfterCompletion = - inputLine.ToString( - replacementEndIndex, - inputLine.Length - replacementEndIndex); - } - else - { - currentCompletion = null; - } - } - } - } - - CompletionResult completion = - currentCompletion?.GetNextResult( - !keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)); - - if (completion != null) - { - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - $"{completion.CompletionText}{inputAfterCompletion}", - currentCursorIndex, - insertIndex: currentCompletion.ReplacementIndex, - replaceLength: inputLine.Length - currentCompletion.ReplacementIndex, - finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length); - } - } - else if (keyInfo.Key == ConsoleKey.LeftArrow) - { - currentCompletion = null; - - if (currentCursorIndex > 0) - { - currentCursorIndex = - this.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - currentCursorIndex - 1); - } - } - else if (keyInfo.Key == ConsoleKey.Home) - { - currentCompletion = null; - - currentCursorIndex = - this.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - 0); - } - else if (keyInfo.Key == ConsoleKey.RightArrow) - { - currentCompletion = null; - - if (currentCursorIndex < inputLine.Length) - { - currentCursorIndex = - this.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - currentCursorIndex + 1); - } - } - else if (keyInfo.Key == ConsoleKey.End) - { - currentCompletion = null; - - currentCursorIndex = - this.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - inputLine.Length); - } - else if (keyInfo.Key == ConsoleKey.UpArrow && isCommandLine) - { - currentCompletion = null; - - // TODO: Ctrl+Up should allow navigation in multi-line input - - if (currentHistory == null) - { - historyIndex = -1; - - PSCommand command = new PSCommand(); - command.AddCommand("Get-History"); - - currentHistory = - await this.powerShellContext.ExecuteCommandAsync( - command, - false, - false) as Collection; - - if (currentHistory != null) - { - historyIndex = currentHistory.Count; - } - } - - if (currentHistory != null && currentHistory.Count > 0 && historyIndex > 0) - { - historyIndex--; - - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - (string)currentHistory[historyIndex].Properties["CommandLine"].Value, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - } - else if (keyInfo.Key == ConsoleKey.DownArrow && isCommandLine) - { - currentCompletion = null; - - // The down arrow shouldn't cause history to be loaded, - // it's only for navigating an active history array - - if (historyIndex > -1 && historyIndex < currentHistory.Count && - currentHistory != null && currentHistory.Count > 0) - { - historyIndex++; - - if (historyIndex < currentHistory.Count) - { - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - (string)currentHistory[historyIndex].Properties["CommandLine"].Value, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - else if (historyIndex == currentHistory.Count) - { - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - } - } - else if (keyInfo.Key == ConsoleKey.Escape) - { - currentCompletion = null; - historyIndex = currentHistory != null ? currentHistory.Count : -1; - - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - else if (keyInfo.Key == ConsoleKey.Backspace) - { - currentCompletion = null; - - if (currentCursorIndex > 0) - { - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: currentCursorIndex - 1, - replaceLength: 1, - finalCursorIndex: currentCursorIndex - 1); - } - } - else if (keyInfo.Key == ConsoleKey.Delete) - { - currentCompletion = null; - - if (currentCursorIndex < inputLine.Length) - { - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - replaceLength: 1, - finalCursorIndex: currentCursorIndex); - } - } - else if (keyInfo.Key == ConsoleKey.Enter) - { - string completedInput = inputLine.ToString(); - currentCompletion = null; - currentHistory = null; - - //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) - //{ - // // TODO: Start a new line! - // continue; - //} - - Parser.ParseInput( - completedInput, - out Token[] tokens, - out ParseError[] parseErrors); - - //if (parseErrors.Any(e => e.IncompleteInput)) - //{ - // // TODO: Start a new line! - // continue; - //} - - return completedInput; - } - else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) - { - // Normal character input - currentCompletion = null; - - currentCursorIndex = - this.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - keyInfo.KeyChar.ToString(), - currentCursorIndex, - finalCursorIndex: currentCursorIndex + 1); - } - } - } - finally - { - Console.TreatControlCAsInput = false; - } - - return null; - } - - private int CalculateIndexFromCursor( - int promptStartCol, - int promptStartRow, - int consoleWidth) - { - return - ((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) + - ConsoleProxy.GetCursorLeft() - promptStartCol; - } - - private void CalculateCursorFromIndex( - int promptStartCol, - int promptStartRow, - int consoleWidth, - int inputIndex, - out int cursorCol, - out int cursorRow) - { - cursorCol = promptStartCol + inputIndex; - cursorRow = promptStartRow + cursorCol / consoleWidth; - cursorCol = cursorCol % consoleWidth; - } - - private int InsertInput( - StringBuilder inputLine, - int promptStartCol, - int promptStartRow, - string insertedInput, - int cursorIndex, - int insertIndex = -1, - int replaceLength = 0, - int finalCursorIndex = -1) - { - int consoleWidth = Console.WindowWidth; - int previousInputLength = inputLine.Length; - - if (insertIndex == -1) - { - insertIndex = cursorIndex; - } - - // Move the cursor to the new insertion point - this.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - insertIndex); - - // Edit the input string based on the insertion - if (insertIndex < inputLine.Length) - { - if (replaceLength > 0) - { - inputLine.Remove(insertIndex, replaceLength); - } - - inputLine.Insert(insertIndex, insertedInput); - } - else - { - inputLine.Append(insertedInput); - } - - // Re-render affected section - Console.Write( - inputLine.ToString( - insertIndex, - inputLine.Length - insertIndex)); - - if (inputLine.Length < previousInputLength) - { - Console.Write( - new string( - ' ', - previousInputLength - inputLine.Length)); - } - - // Automatically set the final cursor position to the end - // of the new input string. This is needed if the previous - // input string is longer than the new one and needed to have - // its old contents overwritten. This will position the cursor - // back at the end of the new text - if (finalCursorIndex == -1 && inputLine.Length < previousInputLength) - { - finalCursorIndex = inputLine.Length; - } - - if (finalCursorIndex > -1) - { - // Move the cursor to the final position - return - this.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - finalCursorIndex); - } - else - { - return inputLine.Length; - } - } - - private int MoveCursorToIndex( - int promptStartCol, - int promptStartRow, - int consoleWidth, - int newCursorIndex) - { - this.CalculateCursorFromIndex( - promptStartCol, - promptStartRow, - consoleWidth, - newCursorIndex, - out int newCursorCol, - out int newCursorRow); - - Console.SetCursorPosition(newCursorCol, newCursorRow); - - return newCursorIndex; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/CredentialFieldDetails.cs b/src/PowerShellEditorServices/Console/CredentialFieldDetails.cs deleted file mode 100644 index 4b4452f2b..000000000 --- a/src/PowerShellEditorServices/Console/CredentialFieldDetails.cs +++ /dev/null @@ -1,122 +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.Management.Automation; -using System.Security; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Contains the details of a PSCredential field shown - /// from an InputPromptHandler. This class is meant to - /// be serializable to the user's UI. - /// - public class CredentialFieldDetails : FieldDetails - { - private string userName; - private SecureString password; - - /// - /// Creates an instance of the CredentialFieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The initial value of the userName field. - public CredentialFieldDetails( - string name, - string label, - string userName) - : this(name, label, typeof(PSCredential), true, null) - { - if (!string.IsNullOrEmpty(userName)) - { - // Call GetNextField to prepare the password field - this.userName = userName; - this.GetNextField(); - } - } - - /// - /// Creates an instance of the CredentialFieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The field's value type. - /// If true, marks the field as mandatory. - /// The field's default value. - public CredentialFieldDetails( - string name, - string label, - Type fieldType, - bool isMandatory, - object defaultValue) - : base(name, label, fieldType, isMandatory, defaultValue) - { - this.Name = "User"; - this.FieldType = typeof(string); - } - - #region Public Methods - - /// - /// Gets the next field to display if this is a complex - /// field, otherwise returns null. - /// - /// - /// A FieldDetails object if there's another field to - /// display or if this field is complete. - /// - public override FieldDetails GetNextField() - { - if (this.password != null) - { - // No more fields to display - return null; - } - else if (this.userName != null) - { - this.Name = $"Password for user {this.userName}"; - this.FieldType = typeof(SecureString); - } - - return this; - } - - /// - /// Sets the field's value. - /// - /// The field's value. - /// - /// True if a value has been supplied by the user, false if the user supplied no value. - /// - public override void SetValue(object fieldValue, bool hasValue) - { - if (hasValue) - { - if (this.userName == null) - { - this.userName = (string)fieldValue; - } - else - { - this.password = (SecureString)fieldValue; - } - } - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - protected override object OnGetValue() - { - return new PSCredential(this.userName, this.password); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/FieldDetails.cs b/src/PowerShellEditorServices/Console/FieldDetails.cs deleted file mode 100644 index ffec536b3..000000000 --- a/src/PowerShellEditorServices/Console/FieldDetails.cs +++ /dev/null @@ -1,235 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Reflection; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Contains the details of an input field shown from an - /// InputPromptHandler. This class is meant to be - /// serializable to the user's UI. - /// - public class FieldDetails - { - #region Private Fields - - private object fieldValue; - - #endregion - - #region Properties - - /// - /// Gets or sets the name of the field. - /// - public string Name { get; set; } - - /// - /// Gets or sets the original name of the field before it was manipulated. - /// - public string OriginalName { get; set; } - - /// - /// Gets or sets the descriptive label for the field. - /// - public string Label { get; set; } - - /// - /// Gets or sets the field's value type. - /// - public Type FieldType { get; set; } - - /// - /// Gets or sets the field's help message. - /// - public string HelpMessage { get; set; } - - /// - /// Gets or sets a boolean that is true if the user - /// must enter a value for the field. - /// - public bool IsMandatory { get; set; } - - /// - /// Gets or sets the default value for the field. - /// - public object DefaultValue { get; set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the FieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The field's value type. - /// If true, marks the field as mandatory. - /// The field's default value. - public FieldDetails( - string name, - string label, - Type fieldType, - bool isMandatory, - object defaultValue) - { - this.OriginalName = name; - this.Name = name; - this.Label = label; - this.FieldType = fieldType; - this.IsMandatory = isMandatory; - this.DefaultValue = defaultValue; - - if (fieldType.GetTypeInfo().IsGenericType) - { - throw new PSArgumentException( - "Generic types are not supported for input fields at this time."); - } - } - - #endregion - - #region Public Methods - - /// - /// Sets the field's value. - /// - /// The field's value. - /// - /// True if a value has been supplied by the user, false if the user supplied no value. - /// - public virtual void SetValue(object fieldValue, bool hasValue) - { - if (hasValue) - { - this.fieldValue = fieldValue; - } - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - public object GetValue(ILogger logger) - { - object fieldValue = this.OnGetValue(); - - if (fieldValue == null) - { - if (!this.IsMandatory) - { - fieldValue = this.DefaultValue; - } - else - { - // This "shoudln't" happen, so log in case it does - logger.Write( - LogLevel.Error, - $"Cannot retrieve value for field {this.Label}"); - } - } - - return fieldValue; - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - protected virtual object OnGetValue() - { - return this.fieldValue; - } - - /// - /// Gets the next field if this field can accept multiple - /// values, like a collection or an object with multiple - /// properties. - /// - /// - /// A new FieldDetails instance if there is a next field - /// or null otherwise. - /// - public virtual FieldDetails GetNextField() - { - return null; - } - - #endregion - - #region Internal Methods - - internal static FieldDetails Create( - FieldDescription fieldDescription, - ILogger logger) - { - Type fieldType = - GetFieldTypeFromTypeName( - fieldDescription.ParameterAssemblyFullName, - logger); - - if (typeof(IList).GetTypeInfo().IsAssignableFrom(fieldType.GetTypeInfo())) - { - return new CollectionFieldDetails( - fieldDescription.Name, - fieldDescription.Label, - fieldType, - fieldDescription.IsMandatory, - fieldDescription.DefaultValue); - } - else if (typeof(PSCredential) == fieldType) - { - return new CredentialFieldDetails( - fieldDescription.Name, - fieldDescription.Label, - fieldType, - fieldDescription.IsMandatory, - fieldDescription.DefaultValue); - } - else - { - return new FieldDetails( - fieldDescription.Name, - fieldDescription.Label, - fieldType, - fieldDescription.IsMandatory, - fieldDescription.DefaultValue); - } - } - - private static Type GetFieldTypeFromTypeName( - string assemblyFullName, - ILogger logger) - { - Type fieldType = typeof(string); - - if (!string.IsNullOrEmpty(assemblyFullName)) - { - if (!LanguagePrimitives.TryConvertTo(assemblyFullName, out fieldType)) - { - logger.Write( - LogLevel.Warning, - string.Format( - "Could not resolve type of field: {0}", - assemblyFullName)); - } - } - - return fieldType; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Console/IConsoleOperations.cs b/src/PowerShellEditorServices/Console/IConsoleOperations.cs deleted file mode 100644 index b3fb58561..000000000 --- a/src/PowerShellEditorServices/Console/IConsoleOperations.cs +++ /dev/null @@ -1,140 +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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides platform specific console utilities. - /// - public interface IConsoleOperations - { - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// An object that describes the constant and Unicode character, if any, - /// that correspond to the pressed console key. The object also - /// describes, in a bitwise combination of values, whether - /// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key. - /// - ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken); - - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// A task that will complete with a result of the key pressed by the user. - /// - Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The horizontal position of the console cursor. - int GetCursorLeft(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The horizontal position of the console cursor. - int GetCursorLeft(CancellationToken cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - Task GetCursorLeftAsync(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - Task GetCursorLeftAsync(CancellationToken cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The vertical position of the console cursor. - int GetCursorTop(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The vertical position of the console cursor. - int GetCursorTop(CancellationToken cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - Task GetCursorTopAsync(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - Task GetCursorTopAsync(CancellationToken cancellationToken); - } -} diff --git a/src/PowerShellEditorServices/Console/InputPromptHandler.cs b/src/PowerShellEditorServices/Console/InputPromptHandler.cs deleted file mode 100644 index 7d626c9ab..000000000 --- a/src/PowerShellEditorServices/Console/InputPromptHandler.cs +++ /dev/null @@ -1,331 +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.Globalization; -using System.Management.Automation; -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides a base implementation for IPromptHandler classes - /// that present the user a set of fields for which values - /// should be entered. - /// - public abstract class InputPromptHandler : PromptHandler - { - #region Private Fields - - private int currentFieldIndex = -1; - private FieldDetails currentField; - private CancellationTokenSource promptCancellationTokenSource = - new CancellationTokenSource(); - private TaskCompletionSource> cancelTask = - new TaskCompletionSource>(); - - #endregion - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public InputPromptHandler(ILogger logger) : base(logger) - { - } - - #region Properties - - /// - /// Gets the array of fields for which the user must enter values. - /// - protected FieldDetails[] Fields { get; private set; } - - #endregion - - #region Public Methods - - /// - /// Prompts the user for a line of input without writing any message or caption. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - public Task PromptForInputAsync( - CancellationToken cancellationToken) - { - Task> innerTask = - this.PromptForInputAsync( - null, - null, - new FieldDetails[] { new FieldDetails("", "", typeof(string), false, "") }, - cancellationToken); - - return - innerTask.ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - // Return the value of the sole field - return (string)task.Result[""]; - }); - } - - /// - /// Prompts the user for a line (or lines) of input. - /// - /// - /// A title shown before the series of input fields. - /// - /// - /// A descritpive message shown before the series of input fields. - /// - /// - /// An array of FieldDetails items to be displayed which prompt the - /// user for input of a specific type. - /// - /// - /// A CancellationToken that can be used to cancel the prompt. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - public async Task> PromptForInputAsync( - string promptCaption, - string promptMessage, - FieldDetails[] fields, - CancellationToken cancellationToken) - { - // Cancel the prompt if the caller cancels the task - cancellationToken.Register(this.CancelPrompt, true); - - this.Fields = fields; - - this.ShowPromptMessage(promptCaption, promptMessage); - - Task> promptTask = - this.StartPromptLoopAsync(this.promptCancellationTokenSource.Token); - - Task finishedTask = - await Task.WhenAny( - cancelTask.Task, - promptTask); - - if (this.cancelTask.Task.IsCanceled) - { - throw new PipelineStoppedException(); - } - - return promptTask.Result; - } - - /// - /// Prompts the user for a SecureString without writing any message or caption. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - public Task PromptForSecureInputAsync( - CancellationToken cancellationToken) - { - Task> innerTask = - this.PromptForInputAsync( - null, - null, - new FieldDetails[] { new FieldDetails("", "", typeof(SecureString), false, "") }, - cancellationToken); - - return - innerTask.ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - // Return the value of the sole field - return (SecureString)task.Result?[""]; - }); - } - - /// - /// Called when the active prompt should be cancelled. - /// - protected override void OnPromptCancelled() - { - // Cancel the prompt task - this.promptCancellationTokenSource.Cancel(); - this.cancelTask.TrySetCanceled(); - } - - #endregion - - #region Abstract Methods - - /// - /// Called when the prompt caption and message should be - /// displayed to the user. - /// - /// The caption string to be displayed. - /// The message string to be displayed. - protected abstract void ShowPromptMessage(string caption, string message); - - /// - /// Called when a prompt should be displayed for a specific - /// input field. - /// - /// The details of the field to be displayed. - protected abstract void ShowFieldPrompt(FieldDetails fieldDetails); - - /// - /// Reads an input string asynchronously from the console. - /// - /// - /// A CancellationToken that can be used to cancel the read. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - protected abstract Task ReadInputStringAsync(CancellationToken cancellationToken); - - /// - /// Reads a SecureString asynchronously from the console. - /// - /// - /// A CancellationToken that can be used to cancel the read. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - protected abstract Task ReadSecureStringAsync(CancellationToken cancellationToken); - - /// - /// Called when an error should be displayed, such as when the - /// user types in a string with an incorrect format for the - /// current field. - /// - /// - /// The Exception containing the error to be displayed. - /// - protected abstract void ShowErrorMessage(Exception e); - - #endregion - - #region Private Methods - - private async Task> StartPromptLoopAsync( - CancellationToken cancellationToken) - { - this.GetNextField(); - - // Loop until there are no more prompts to process - while (this.currentField != null && !cancellationToken.IsCancellationRequested) - { - // Show current prompt - this.ShowFieldPrompt(this.currentField); - - bool enteredValue = false; - object responseValue = null; - string responseString = null; - - // Read input depending on field type - if (this.currentField.FieldType == typeof(SecureString)) - { - SecureString secureString = await this.ReadSecureStringAsync(cancellationToken); - responseValue = secureString; - enteredValue = secureString != null; - } - else - { - responseString = await this.ReadInputStringAsync(cancellationToken); - responseValue = responseString; - enteredValue = responseString != null && responseString.Length > 0; - - try - { - responseValue = - LanguagePrimitives.ConvertTo( - responseString, - this.currentField.FieldType, - CultureInfo.CurrentCulture); - } - catch (PSInvalidCastException e) - { - this.ShowErrorMessage(e.InnerException ?? e); - continue; - } - } - - // Set the field's value and get the next field - this.currentField.SetValue(responseValue, enteredValue); - this.GetNextField(); - } - - if (cancellationToken.IsCancellationRequested) - { - // Throw a TaskCanceledException to stop the pipeline - throw new TaskCanceledException(); - } - - // Return the field values - return this.GetFieldValues(); - } - - private FieldDetails GetNextField() - { - FieldDetails nextField = this.currentField?.GetNextField(); - - if (nextField == null) - { - this.currentFieldIndex++; - - // Have we shown all the prompts already? - if (this.currentFieldIndex < this.Fields.Length) - { - nextField = this.Fields[this.currentFieldIndex]; - } - } - - this.currentField = nextField; - return nextField; - } - - private Dictionary GetFieldValues() - { - Dictionary fieldValues = new Dictionary(); - - foreach (FieldDetails field in this.Fields) - { - fieldValues.Add(field.OriginalName, field.GetValue(this.Logger)); - } - - return fieldValues; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/PromptHandler.cs b/src/PowerShellEditorServices/Console/PromptHandler.cs deleted file mode 100644 index a40bd6e76..000000000 --- a/src/PowerShellEditorServices/Console/PromptHandler.cs +++ /dev/null @@ -1,55 +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 Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Defines an abstract base class for prompt handler implementations. - /// - public abstract class PromptHandler - { - /// - /// Gets the ILogger implementation used for this instance. - /// - protected ILogger Logger { get; private set; } - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public PromptHandler(ILogger logger) - { - this.Logger = logger; - } - - /// - /// Called when the active prompt should be cancelled. - /// - public void CancelPrompt() - { - // Allow the implementation to clean itself up - this.OnPromptCancelled(); - this.PromptCancelled?.Invoke(this, new EventArgs()); - } - - /// - /// An event that gets raised if the prompt is cancelled, either - /// by the user or due to a timeout. - /// - public event EventHandler PromptCancelled; - - /// - /// Implementation classes may override this method to perform - /// cleanup when the CancelPrompt method gets called. - /// - protected virtual void OnPromptCancelled() - { - } - } -} - diff --git a/src/PowerShellEditorServices/Console/TerminalChoicePromptHandler.cs b/src/PowerShellEditorServices/Console/TerminalChoicePromptHandler.cs deleted file mode 100644 index 1bf5a5cc4..000000000 --- a/src/PowerShellEditorServices/Console/TerminalChoicePromptHandler.cs +++ /dev/null @@ -1,63 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides a standard implementation of ChoicePromptHandler - /// for use in the interactive console (REPL). - /// - internal class TerminalChoicePromptHandler : ConsoleChoicePromptHandler - { - #region Private Fields - - private ConsoleReadLine consoleReadLine; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleChoicePromptHandler class. - /// - /// - /// The ConsoleReadLine instance to use for interacting with the terminal. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public TerminalChoicePromptHandler( - ConsoleReadLine consoleReadLine, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - this.hostOutput = hostOutput; - this.consoleReadLine = consoleReadLine; - } - - #endregion - - /// - /// Reads an input string from the user. - /// - /// A CancellationToken that can be used to cancel the prompt. - /// A Task that can be awaited to get the user's response. - protected override async Task ReadInputStringAsync(CancellationToken cancellationToken) - { - string inputString = await this.consoleReadLine.ReadSimpleLineAsync(cancellationToken); - this.hostOutput.WriteOutput(string.Empty); - - return inputString; - } - } -} diff --git a/src/PowerShellEditorServices/Console/TerminalInputPromptHandler.cs b/src/PowerShellEditorServices/Console/TerminalInputPromptHandler.cs deleted file mode 100644 index 67b58bc55..000000000 --- a/src/PowerShellEditorServices/Console/TerminalInputPromptHandler.cs +++ /dev/null @@ -1,80 +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.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - /// - /// Provides a standard implementation of InputPromptHandler - /// for use in the interactive console (REPL). - /// - internal class TerminalInputPromptHandler : ConsoleInputPromptHandler - { - #region Private Fields - - private ConsoleReadLine consoleReadLine; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleInputPromptHandler class. - /// - /// - /// The ConsoleReadLine instance to use for interacting with the terminal. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public TerminalInputPromptHandler( - ConsoleReadLine consoleReadLine, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - this.consoleReadLine = consoleReadLine; - } - - #endregion - - #region Public Methods - - /// - /// Reads an input string from the user. - /// - /// A CancellationToken that can be used to cancel the prompt. - /// A Task that can be awaited to get the user's response. - protected override async Task ReadInputStringAsync(CancellationToken cancellationToken) - { - string inputString = await this.consoleReadLine.ReadSimpleLineAsync(cancellationToken); - this.hostOutput.WriteOutput(string.Empty); - - return inputString; - } - - /// - /// Reads a SecureString from the user. - /// - /// A CancellationToken that can be used to cancel the prompt. - /// A Task that can be awaited to get the user's response. - protected override async Task ReadSecureStringAsync(CancellationToken cancellationToken) - { - SecureString secureString = await this.consoleReadLine.ReadSecureLineAsync(cancellationToken); - this.hostOutput.WriteOutput(string.Empty); - - return secureString; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Console/UnixConsoleOperations.cs b/src/PowerShellEditorServices/Console/UnixConsoleOperations.cs deleted file mode 100644 index 199f312f2..000000000 --- a/src/PowerShellEditorServices/Console/UnixConsoleOperations.cs +++ /dev/null @@ -1,284 +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.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; -using UnixConsoleEcho; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - internal class UnixConsoleOperations : IConsoleOperations - { - private const int LongWaitForKeySleepTime = 300; - - private const int ShortWaitForKeyTimeout = 5000; - - private const int ShortWaitForKeySpinUntilSleepTime = 30; - - private static readonly ManualResetEventSlim s_waitHandle = new ManualResetEventSlim(); - - private static readonly SemaphoreSlim s_readKeyHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - private static readonly SemaphoreSlim s_stdInHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - private Func WaitForKeyAvailable; - - private Func> WaitForKeyAvailableAsync; - - internal UnixConsoleOperations() - { - // Switch between long and short wait periods depending on if the - // user has recently (last 5 seconds) pressed a key to avoid preventing - // the CPU from entering low power mode. - WaitForKeyAvailable = LongWaitForKey; - WaitForKeyAvailableAsync = LongWaitForKeyAsync; - } - - public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) - { - s_readKeyHandle.Wait(cancellationToken); - - // On Unix platforms System.Console.ReadKey has an internal lock on stdin. Because - // of this, if a ReadKey call is pending in one thread and in another thread - // Console.CursorLeft is called, both threads block until a key is pressed. - - // To work around this we wait for a key to be pressed before actually calling Console.ReadKey. - // However, any pressed keys during this time will be echoed to the console. To get around - // this we use the UnixConsoleEcho package to disable echo prior to waiting. - InputEcho.Disable(); - try - { - // The WaitForKeyAvailable delegate switches between a long delay between waits and - // a short timeout depending on how recently a key has been pressed. This allows us - // to let the CPU enter low power mode without compromising responsiveness. - while (!WaitForKeyAvailable(cancellationToken)); - } - finally - { - InputEcho.Disable(); - s_readKeyHandle.Release(); - } - - // A key has been pressed, so aquire a lock on our internal stdin handle. This is done - // so any of our calls to cursor position API's do not release ReadKey. - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.ReadKey(intercept); - } - finally - { - s_stdInHandle.Release(); - } - } - - public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) - { - await s_readKeyHandle.WaitAsync(cancellationToken); - - // I tried to replace this library with a call to `stty -echo`, but unfortunately - // the library also sets up allowing backspace to trigger `Console.KeyAvailable`. - InputEcho.Disable(); - try - { - while (!await WaitForKeyAvailableAsync(cancellationToken)); - } - finally - { - InputEcho.Enable(); - s_readKeyHandle.Release(); - } - - await s_stdInHandle.WaitAsync(cancellationToken); - try - { - return System.Console.ReadKey(intercept); - } - finally - { - s_stdInHandle.Release(); - } - } - - public int GetCursorLeft() - { - return GetCursorLeft(CancellationToken.None); - } - - public int GetCursorLeft(CancellationToken cancellationToken) - { - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.CursorLeft; - } - finally - { - s_stdInHandle.Release(); - } - } - - public async Task GetCursorLeftAsync() - { - return await GetCursorLeftAsync(CancellationToken.None); - } - - public async Task GetCursorLeftAsync(CancellationToken cancellationToken) - { - await s_stdInHandle.WaitAsync(cancellationToken); - try - { - return System.Console.CursorLeft; - } - finally - { - s_stdInHandle.Release(); - } - } - - public int GetCursorTop() - { - return GetCursorTop(CancellationToken.None); - } - - public int GetCursorTop(CancellationToken cancellationToken) - { - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.CursorTop; - } - finally - { - s_stdInHandle.Release(); - } - } - - public async Task GetCursorTopAsync() - { - return await GetCursorTopAsync(CancellationToken.None); - } - - public async Task GetCursorTopAsync(CancellationToken cancellationToken) - { - await s_stdInHandle.WaitAsync(cancellationToken); - try - { - return System.Console.CursorTop; - } - finally - { - s_stdInHandle.Release(); - } - } - - private bool LongWaitForKey(CancellationToken cancellationToken) - { - // Wait for a key to be buffered (in other words, wait for Console.KeyAvailable to become - // true) with a long delay between checks. - while (!IsKeyAvailable(cancellationToken)) - { - s_waitHandle.Wait(LongWaitForKeySleepTime, cancellationToken); - } - - // As soon as a key is buffered, return true and switch the wait logic to be more - // responsive, but also more expensive. - WaitForKeyAvailable = ShortWaitForKey; - return true; - } - - private async Task LongWaitForKeyAsync(CancellationToken cancellationToken) - { - while (!await IsKeyAvailableAsync(cancellationToken)) - { - await Task.Delay(LongWaitForKeySleepTime, cancellationToken); - } - - WaitForKeyAvailableAsync = ShortWaitForKeyAsync; - return true; - } - - private bool ShortWaitForKey(CancellationToken cancellationToken) - { - // Check frequently for a new key to be buffered. - if (SpinUntilKeyAvailable(ShortWaitForKeyTimeout, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - return true; - } - - // If the user has not pressed a key before the end of the SpinUntil timeout then - // the user is idle and we can switch back to long delays between KeyAvailable checks. - cancellationToken.ThrowIfCancellationRequested(); - WaitForKeyAvailable = LongWaitForKey; - return false; - } - - private async Task ShortWaitForKeyAsync(CancellationToken cancellationToken) - { - if (await SpinUntilKeyAvailableAsync(ShortWaitForKeyTimeout, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - return true; - } - - cancellationToken.ThrowIfCancellationRequested(); - WaitForKeyAvailableAsync = LongWaitForKeyAsync; - return false; - } - - private bool SpinUntilKeyAvailable(int millisecondsTimeout, CancellationToken cancellationToken) - { - return SpinWait.SpinUntil( - () => - { - s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); - return IsKeyAvailable(cancellationToken); - }, - millisecondsTimeout); - } - - private async Task SpinUntilKeyAvailableAsync(int millisecondsTimeout, CancellationToken cancellationToken) - { - return await Task.Factory.StartNew( - () => SpinWait.SpinUntil( - () => - { - // The wait handle is never set, it's just used to enable cancelling the wait. - s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); - return IsKeyAvailable(cancellationToken); - }, - millisecondsTimeout)); - } - - private bool IsKeyAvailable(CancellationToken cancellationToken) - { - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.KeyAvailable; - } - finally - { - s_stdInHandle.Release(); - } - } - - private async Task IsKeyAvailableAsync(CancellationToken cancellationToken) - { - await s_stdInHandle.WaitAsync(cancellationToken); - try - { - return System.Console.KeyAvailable; - } - finally - { - s_stdInHandle.Release(); - } - } - } -} diff --git a/src/PowerShellEditorServices/Console/WindowsConsoleOperations.cs b/src/PowerShellEditorServices/Console/WindowsConsoleOperations.cs deleted file mode 100644 index 493e66930..000000000 --- a/src/PowerShellEditorServices/Console/WindowsConsoleOperations.cs +++ /dev/null @@ -1,76 +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.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Console -{ - internal class WindowsConsoleOperations : IConsoleOperations - { - private ConsoleKeyInfo? _bufferedKey; - - private SemaphoreSlim _readKeyHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - public int GetCursorLeft() => System.Console.CursorLeft; - - public int GetCursorLeft(CancellationToken cancellationToken) => System.Console.CursorLeft; - - public Task GetCursorLeftAsync() => Task.FromResult(System.Console.CursorLeft); - - public Task GetCursorLeftAsync(CancellationToken cancellationToken) => Task.FromResult(System.Console.CursorLeft); - - public int GetCursorTop() => System.Console.CursorTop; - - public int GetCursorTop(CancellationToken cancellationToken) => System.Console.CursorTop; - - public Task GetCursorTopAsync() => Task.FromResult(System.Console.CursorTop); - - public Task GetCursorTopAsync(CancellationToken cancellationToken) => Task.FromResult(System.Console.CursorTop); - - public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) - { - await _readKeyHandle.WaitAsync(cancellationToken); - try - { - return - _bufferedKey.HasValue - ? _bufferedKey.Value - : await Task.Factory.StartNew( - () => (_bufferedKey = System.Console.ReadKey(intercept)).Value); - } - finally - { - _readKeyHandle.Release(); - - // Throw if we're cancelled so the buffered key isn't cleared. - cancellationToken.ThrowIfCancellationRequested(); - _bufferedKey = null; - } - } - - public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) - { - _readKeyHandle.Wait(cancellationToken); - try - { - return - _bufferedKey.HasValue - ? _bufferedKey.Value - : (_bufferedKey = System.Console.ReadKey(intercept)).Value; - } - finally - { - _readKeyHandle.Release(); - - // Throw if we're cancelled so the buffered key isn't cleared. - cancellationToken.ThrowIfCancellationRequested(); - _bufferedKey = null; - } - } - } -} diff --git a/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs b/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs deleted file mode 100644 index f89606a2a..000000000 --- a/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs +++ /dev/null @@ -1,108 +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.Management.Automation; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides details about a breakpoint that is set in the - /// PowerShell debugger. - /// - public class BreakpointDetails : BreakpointDetailsBase - { - /// - /// Gets the unique ID of the breakpoint. - /// - /// - public int Id { get; private set; } - - /// - /// Gets the source where the breakpoint is located. Used only for debug purposes. - /// - public string Source { get; private set; } - - /// - /// Gets the line number at which the breakpoint is set. - /// - public int LineNumber { get; private set; } - - /// - /// Gets the column number at which the breakpoint is set. If null, the default of 1 is used. - /// - public int? ColumnNumber { get; private set; } - - private BreakpointDetails() - { - } - - /// - /// Creates an instance of the BreakpointDetails class from the individual - /// pieces of breakpoint information provided by the client. - /// - /// - /// - /// - /// - /// - /// - public static BreakpointDetails Create( - string source, - int line, - int? column = null, - string condition = null, - string hitCondition = null) - { - Validate.IsNotNull("source", source); - - return new BreakpointDetails - { - Verified = true, - Source = source, - LineNumber = line, - ColumnNumber = column, - Condition = condition, - HitCondition = hitCondition - }; - } - - /// - /// Creates an instance of the BreakpointDetails class from a - /// PowerShell Breakpoint object. - /// - /// The Breakpoint instance from which details will be taken. - /// A new instance of the BreakpointDetails class. - public static BreakpointDetails Create(Breakpoint breakpoint) - { - Validate.IsNotNull("breakpoint", breakpoint); - - LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint; - if (lineBreakpoint == null) - { - throw new ArgumentException( - "Unexpected breakpoint type: " + breakpoint.GetType().Name); - } - - var breakpointDetails = new BreakpointDetails - { - Id = breakpoint.Id, - Verified = true, - Source = lineBreakpoint.Script, - LineNumber = lineBreakpoint.Line, - ColumnNumber = lineBreakpoint.Column, - Condition = lineBreakpoint.Action?.ToString() - }; - - if (lineBreakpoint.Column > 0) - { - breakpointDetails.ColumnNumber = lineBreakpoint.Column; - } - - return breakpointDetails; - } - } -} diff --git a/src/PowerShellEditorServices/Debugging/BreakpointDetailsBase.cs b/src/PowerShellEditorServices/Debugging/BreakpointDetailsBase.cs deleted file mode 100644 index 9fa83c23e..000000000 --- a/src/PowerShellEditorServices/Debugging/BreakpointDetailsBase.cs +++ /dev/null @@ -1,36 +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 -{ - /// - /// Provides details about a breakpoint that is set in the - /// PowerShell debugger. - /// - public abstract class BreakpointDetailsBase - { - /// - /// Gets or sets a boolean indicator that if true, breakpoint could be set - /// (but not necessarily at the desired location). - /// - public bool Verified { get; set; } - - /// - /// Gets or set an optional message about the state of the breakpoint. This is shown to the user - /// and can be used to explain why a breakpoint could not be verified. - /// - public string Message { get; set; } - - /// - /// Gets the breakpoint condition string. - /// - public string Condition { get; protected set; } - - /// - /// Gets the breakpoint hit condition string. - /// - public string HitCondition { get; protected set; } - } -} diff --git a/src/PowerShellEditorServices/Debugging/CommandBreakpointDetails.cs b/src/PowerShellEditorServices/Debugging/CommandBreakpointDetails.cs deleted file mode 100644 index 8197480c9..000000000 --- a/src/PowerShellEditorServices/Debugging/CommandBreakpointDetails.cs +++ /dev/null @@ -1,73 +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.Management.Automation; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides details about a command breakpoint that is set in the PowerShell debugger. - /// - public class CommandBreakpointDetails : BreakpointDetailsBase - { - /// - /// Gets the name of the command on which the command breakpoint has been set. - /// - public string Name { get; private set; } - - private CommandBreakpointDetails() - { - } - - /// - /// Creates an instance of the class from the individual - /// pieces of breakpoint information provided by the client. - /// - /// The name of the command to break on. - /// Condition string that would be applied to the breakpoint Action parameter. - /// Hit condition string that would be applied to the breakpoint Action parameter. - /// - public static CommandBreakpointDetails Create( - string name, - string condition = null, - string hitCondition = null) - { - Validate.IsNotNull(nameof(name), name); - - return new CommandBreakpointDetails { - Name = name, - Condition = condition - }; - } - - /// - /// Creates an instance of the class from a - /// PowerShell CommandBreakpoint object. - /// - /// The Breakpoint instance from which details will be taken. - /// A new instance of the BreakpointDetails class. - public static CommandBreakpointDetails Create(Breakpoint breakpoint) - { - Validate.IsNotNull("breakpoint", breakpoint); - - CommandBreakpoint commandBreakpoint = breakpoint as CommandBreakpoint; - if (commandBreakpoint == null) - { - throw new ArgumentException( - "Unexpected breakpoint type: " + breakpoint.GetType().Name); - } - - var breakpointDetails = new CommandBreakpointDetails { - Verified = true, - Name = commandBreakpoint.Command, - Condition = commandBreakpoint.Action?.ToString() - }; - - return breakpointDetails; - } - } -} diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs deleted file mode 100644 index 98d47d7af..000000000 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ /dev/null @@ -1,1359 +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.Linq; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Debugging; -using Microsoft.PowerShell.EditorServices.Utility; -using Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Session.Capabilities; -using System.Threading; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides a high-level service for interacting with the - /// PowerShell debugger in the runspace managed by a PowerShellContext. - /// - public class DebugService - { - #region Fields - - private const string PsesGlobalVariableNamePrefix = "__psEditorServices_"; - private const string TemporaryScriptFileName = "Script Listing.ps1"; - - private ILogger logger; - private PowerShellContext powerShellContext; - private RemoteFileManager remoteFileManager; - - // TODO: This needs to be managed per nested session - private Dictionary> breakpointsPerFile = - new Dictionary>(); - - private int nextVariableId; - private string temporaryScriptListingPath; - private List variables; - private VariableContainerDetails globalScopeVariables; - private VariableContainerDetails scriptScopeVariables; - private StackFrameDetails[] stackFrameDetails; - private PropertyInfo invocationTypeScriptPositionProperty; - - private static int breakpointHitCounter = 0; - - private SemaphoreSlim debugInfoHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - #endregion - - #region Properties - - /// - /// Gets or sets a boolean that indicates whether a debugger client is - /// currently attached to the debugger. - /// - public bool IsClientAttached { get; set; } - - /// - /// Gets a boolean that indicates whether the debugger is currently - /// stopped at a breakpoint. - /// - public bool IsDebuggerStopped => this.powerShellContext.IsDebuggerStopped; - - /// - /// Gets the current DebuggerStoppedEventArgs when the debugger - /// is stopped. - /// - public DebuggerStoppedEventArgs CurrentDebuggerStoppedEventArgs { get; private set; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the DebugService class and uses - /// the given PowerShellContext for all future operations. - /// - /// - /// The PowerShellContext to use for all debugging operations. - /// - /// An ILogger implementation used for writing log messages. - public DebugService(PowerShellContext powerShellContext, ILogger logger) - : this(powerShellContext, null, logger) - { - } - - /// - /// Initializes a new instance of the DebugService class and uses - /// the given PowerShellContext for all future operations. - /// - /// - /// The PowerShellContext to use for all debugging operations. - /// - /// - /// A RemoteFileManager instance to use for accessing files in remote sessions. - /// - /// An ILogger implementation used for writing log messages. - public DebugService( - PowerShellContext powerShellContext, - RemoteFileManager remoteFileManager, - ILogger logger) - { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - this.logger = logger; - this.powerShellContext = powerShellContext; - this.powerShellContext.DebuggerStop += this.OnDebuggerStopAsync; - this.powerShellContext.DebuggerResumed += this.OnDebuggerResumed; - - this.powerShellContext.BreakpointUpdated += this.OnBreakpointUpdated; - - this.remoteFileManager = remoteFileManager; - - this.invocationTypeScriptPositionProperty = - typeof(InvocationInfo) - .GetProperty( - "ScriptPosition", - BindingFlags.NonPublic | BindingFlags.Instance); - } - - #endregion - - #region Public Methods - - /// - /// Sets the list of line breakpoints for the current debugging session. - /// - /// The ScriptFile in which breakpoints will be set. - /// BreakpointDetails for each breakpoint that will be set. - /// If true, causes all existing breakpoints to be cleared before setting new ones. - /// An awaitable Task that will provide details about the breakpoints that were set. - public async Task SetLineBreakpointsAsync( - ScriptFile scriptFile, - BreakpointDetails[] breakpoints, - bool clearExisting = true) - { - var resultBreakpointDetails = new List(); - - var dscBreakpoints = - this.powerShellContext - .CurrentRunspace - .GetCapability(); - - // Make sure we're using the remote script path - string scriptPath = scriptFile.FilePath; - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null) - { - if (!this.remoteFileManager.IsUnderRemoteTempPath(scriptPath)) - { - this.logger.Write( - LogLevel.Verbose, - $"Could not set breakpoints for local path '{scriptPath}' in a remote session."); - - return resultBreakpointDetails.ToArray(); - } - - string mappedPath = - this.remoteFileManager.GetMappedPath( - scriptPath, - this.powerShellContext.CurrentRunspace); - - scriptPath = mappedPath; - } - else if ( - this.temporaryScriptListingPath != null && - this.temporaryScriptListingPath.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase)) - { - this.logger.Write( - LogLevel.Verbose, - $"Could not set breakpoint on temporary script listing path '{scriptPath}'."); - - return resultBreakpointDetails.ToArray(); - } - - // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to - // quoted and have those wildcard chars escaped. - string escapedScriptPath = - PowerShellContext.WildcardEscapePath(scriptPath); - - if (dscBreakpoints == null || !dscBreakpoints.IsDscResourcePath(escapedScriptPath)) - { - if (clearExisting) - { - await this.ClearBreakpointsInFileAsync(scriptFile); - } - - foreach (BreakpointDetails breakpoint in breakpoints) - { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint"); - psCommand.AddParameter("Script", escapedScriptPath); - psCommand.AddParameter("Line", breakpoint.LineNumber); - - // Check if the user has specified the column number for the breakpoint. - if (breakpoint.ColumnNumber.HasValue && breakpoint.ColumnNumber.Value > 0) - { - // It bums me out that PowerShell will silently ignore a breakpoint - // where either the line or the column is invalid. I'd rather have an - // error or warning message I could relay back to the client. - psCommand.AddParameter("Column", breakpoint.ColumnNumber.Value); - } - - // Check if this is a "conditional" line breakpoint. - if (!String.IsNullOrWhiteSpace(breakpoint.Condition) || - !String.IsNullOrWhiteSpace(breakpoint.HitCondition)) - { - ScriptBlock actionScriptBlock = - GetBreakpointActionScriptBlock(breakpoint); - - // If there was a problem with the condition string, - // move onto the next breakpoint. - if (actionScriptBlock == null) - { - resultBreakpointDetails.Add(breakpoint); - continue; - } - - psCommand.AddParameter("Action", actionScriptBlock); - } - - IEnumerable configuredBreakpoints = - await this.powerShellContext.ExecuteCommandAsync(psCommand); - - // The order in which the breakpoints are returned is significant to the - // VSCode client and should match the order in which they are passed in. - resultBreakpointDetails.AddRange( - configuredBreakpoints.Select(BreakpointDetails.Create)); - } - } - else - { - resultBreakpointDetails = - await dscBreakpoints.SetLineBreakpointsAsync( - this.powerShellContext, - escapedScriptPath, - breakpoints); - } - - return resultBreakpointDetails.ToArray(); - } - - /// - /// Sets the list of command breakpoints for the current debugging session. - /// - /// CommandBreakpointDetails for each command breakpoint that will be set. - /// If true, causes all existing function breakpoints to be cleared before setting new ones. - /// An awaitable Task that will provide details about the breakpoints that were set. - public async Task SetCommandBreakpointsAsync( - CommandBreakpointDetails[] breakpoints, - bool clearExisting = true) - { - var resultBreakpointDetails = new List(); - - if (clearExisting) - { - await this.ClearCommandBreakpointsAsync(); - } - - if (breakpoints.Length > 0) - { - foreach (CommandBreakpointDetails breakpoint in breakpoints) - { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint"); - psCommand.AddParameter("Command", breakpoint.Name); - - // Check if this is a "conditional" command breakpoint. - if (!String.IsNullOrWhiteSpace(breakpoint.Condition) || - !String.IsNullOrWhiteSpace(breakpoint.HitCondition)) - { - ScriptBlock actionScriptBlock = GetBreakpointActionScriptBlock(breakpoint); - - // If there was a problem with the condition string, - // move onto the next breakpoint. - if (actionScriptBlock == null) - { - resultBreakpointDetails.Add(breakpoint); - continue; - } - - psCommand.AddParameter("Action", actionScriptBlock); - } - - IEnumerable configuredBreakpoints = - await this.powerShellContext.ExecuteCommandAsync(psCommand); - - // The order in which the breakpoints are returned is significant to the - // VSCode client and should match the order in which they are passed in. - resultBreakpointDetails.AddRange( - configuredBreakpoints.Select(CommandBreakpointDetails.Create)); - } - } - - return resultBreakpointDetails.ToArray(); - } - - /// - /// Sends a "continue" action to the debugger when stopped. - /// - public void Continue() - { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.Continue); - } - - /// - /// Sends a "step over" action to the debugger when stopped. - /// - public void StepOver() - { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.StepOver); - } - - /// - /// Sends a "step in" action to the debugger when stopped. - /// - public void StepIn() - { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.StepInto); - } - - /// - /// Sends a "step out" action to the debugger when stopped. - /// - public void StepOut() - { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.StepOut); - } - - /// - /// Causes the debugger to break execution wherever it currently - /// is at the time. This is equivalent to clicking "Pause" in a - /// debugger UI. - /// - public void Break() - { - // Break execution in the debugger - this.powerShellContext.BreakExecution(); - } - - /// - /// Aborts execution of the debugger while it is running, even while - /// it is stopped. Equivalent to calling PowerShellContext.AbortExecution. - /// - public void Abort() - { - this.powerShellContext.AbortExecution(shouldAbortDebugSession: true); - } - - /// - /// Gets the list of variables that are children of the scope or variable - /// that is identified by the given referenced ID. - /// - /// - /// An array of VariableDetails instances which describe the requested variables. - public VariableDetailsBase[] GetVariables(int variableReferenceId) - { - VariableDetailsBase[] childVariables; - this.debugInfoHandle.Wait(); - try - { - if ((variableReferenceId < 0) || (variableReferenceId >= this.variables.Count)) - { - logger.Write(LogLevel.Warning, $"Received request for variableReferenceId {variableReferenceId} that is out of range of valid indices."); - return new VariableDetailsBase[0]; - } - - VariableDetailsBase parentVariable = this.variables[variableReferenceId]; - if (parentVariable.IsExpandable) - { - childVariables = parentVariable.GetChildren(this.logger); - foreach (var child in childVariables) - { - // Only add child if it hasn't already been added. - if (child.Id < 0) - { - child.Id = this.nextVariableId++; - this.variables.Add(child); - } - } - } - else - { - childVariables = new VariableDetailsBase[0]; - } - - return childVariables; - } - finally - { - this.debugInfoHandle.Release(); - } - } - - /// - /// Evaluates a variable expression in the context of the stopped - /// debugger. This method decomposes the variable expression to - /// walk the cached variable data for the specified stack frame. - /// - /// The variable expression string to evaluate. - /// The ID of the stack frame in which the expression should be evaluated. - /// A VariableDetailsBase object containing the result. - public VariableDetailsBase GetVariableFromExpression(string variableExpression, int stackFrameId) - { - // NOTE: From a watch we will get passed expressions that are not naked variables references. - // Probably the right way to do this woudld be to examine the AST of the expr before calling - // this method to make sure it is a VariableReference. But for the most part, non-naked variable - // references are very unlikely to find a matching variable e.g. "$i+5.2" will find no var matching "$i+5". - - // Break up the variable path - string[] variablePathParts = variableExpression.Split('.'); - - VariableDetailsBase resolvedVariable = null; - IEnumerable variableList; - - // Ensure debug info isn't currently being built. - this.debugInfoHandle.Wait(); - try - { - variableList = this.variables; - } - finally - { - this.debugInfoHandle.Release(); - } - - foreach (var variableName in variablePathParts) - { - if (variableList == null) - { - // If there are no children left to search, break out early - return null; - } - - resolvedVariable = - variableList.FirstOrDefault( - v => - string.Equals( - v.Name, - variableName, - StringComparison.CurrentCultureIgnoreCase)); - - if (resolvedVariable != null && - resolvedVariable.IsExpandable) - { - // Continue by searching in this variable's children - variableList = this.GetVariables(resolvedVariable.Id); - } - } - - return resolvedVariable; - } - - /// - /// Sets the specified variable by container variableReferenceId and variable name to the - /// specified new value. If the variable cannot be set or converted to that value this - /// method will throw InvalidPowerShellExpressionException, ArgumentTransformationMetadataException, or - /// SessionStateUnauthorizedAccessException. - /// - /// The container (Autos, Local, Script, Global) that holds the variable. - /// The name of the variable prefixed with $. - /// The new string value. This value must not be null. If you want to set the variable to $null - /// pass in the string "$null". - /// The string representation of the value the variable was set to. - public async Task SetVariableAsync(int variableContainerReferenceId, string name, string value) - { - Validate.IsNotNull(nameof(name), name); - Validate.IsNotNull(nameof(value), value); - - this.logger.Write(LogLevel.Verbose, $"SetVariableRequest for '{name}' to value string (pre-quote processing): '{value}'"); - - // An empty or whitespace only value is not a valid expression for SetVariable. - if (value.Trim().Length == 0) - { - throw new InvalidPowerShellExpressionException("Expected an expression."); - } - - // Evaluate the expression to get back a PowerShell object from the expression string. - PSCommand psCommand = new PSCommand(); - psCommand.AddScript(value); - var errorMessages = new StringBuilder(); - var results = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, - errorMessages, - false, - false); - - // Check if PowerShell's evaluation of the expression resulted in an error. - object psobject = results.FirstOrDefault(); - if ((psobject == null) && (errorMessages.Length > 0)) - { - throw new InvalidPowerShellExpressionException(errorMessages.ToString()); - } - - // If PowerShellContext.ExecuteCommand returns an ErrorRecord as output, the expression failed evaluation. - // Ideally we would have a separate means from communicating error records apart from normal output. - ErrorRecord errorRecord = psobject as ErrorRecord; - if (errorRecord != null) - { - throw new InvalidPowerShellExpressionException(errorRecord.ToString()); - } - - // OK, now we have a PS object from the supplied value string (expression) to assign to a variable. - // Get the variable referenced by variableContainerReferenceId and variable name. - VariableContainerDetails variableContainer = null; - await this.debugInfoHandle.WaitAsync(); - try - { - variableContainer = (VariableContainerDetails)this.variables[variableContainerReferenceId]; - } - finally - { - this.debugInfoHandle.Release(); - } - - VariableDetailsBase variable = variableContainer.Children[name]; - // Determine scope in which the variable lives. This is required later for the call to Get-Variable -Scope. - string scope = null; - if (variableContainerReferenceId == this.scriptScopeVariables.Id) - { - scope = "Script"; - } - else if (variableContainerReferenceId == this.globalScopeVariables.Id) - { - scope = "Global"; - } - else - { - // Determine which stackframe's local scope the variable is in. - StackFrameDetails[] stackFrames = await this.GetStackFramesAsync(); - for (int i = 0; i < stackFrames.Length; i++) - { - var stackFrame = stackFrames[i]; - if (stackFrame.LocalVariables.ContainsVariable(variable.Id)) - { - scope = i.ToString(); - break; - } - } - } - - if (scope == null) - { - // Hmm, this would be unexpected. No scope means do not pass GO, do not collect $200. - throw new Exception("Could not find the scope for this variable."); - } - - // Now that we have the scope, get the associated PSVariable object for the variable to be set. - psCommand.Commands.Clear(); - psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable"); - psCommand.AddParameter("Name", name.TrimStart('$')); - psCommand.AddParameter("Scope", scope); - - IEnumerable result = await this.powerShellContext.ExecuteCommandAsync(psCommand, sendErrorToHost: false); - PSVariable psVariable = result.FirstOrDefault(); - if (psVariable == null) - { - throw new Exception($"Failed to retrieve PSVariable object for '{name}' from scope '{scope}'."); - } - - // We have the PSVariable object for the variable the user wants to set and an object to assign to that variable. - // The last step is to determine whether the PSVariable is "strongly typed" which may require a conversion. - // If it is not strongly typed, we simply assign the object directly to the PSVariable potentially changing its type. - // Turns out ArgumentTypeConverterAttribute is not public. So we call the attribute through it's base class - - // ArgumentTransformationAttribute. - var argTypeConverterAttr = - psVariable.Attributes - .OfType() - .FirstOrDefault(a => a.GetType().Name.Equals("ArgumentTypeConverterAttribute")); - - if (argTypeConverterAttr != null) - { - // PSVariable is strongly typed. Need to apply the conversion/transform to the new value. - psCommand.Commands.Clear(); - psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable"); - psCommand.AddParameter("Name", "ExecutionContext"); - psCommand.AddParameter("ValueOnly"); - - errorMessages.Clear(); - - var getExecContextResults = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, - errorMessages, - sendErrorToHost: false); - - EngineIntrinsics executionContext = getExecContextResults.OfType().FirstOrDefault(); - - var msg = $"Setting variable '{name}' using conversion to value: {psobject ?? ""}"; - this.logger.Write(LogLevel.Verbose, msg); - - psVariable.Value = argTypeConverterAttr.Transform(executionContext, psobject); - } - else - { - // PSVariable is *not* strongly typed. In this case, whack the old value with the new value. - var msg = $"Setting variable '{name}' directly to value: {psobject ?? ""} - previous type was {psVariable.Value?.GetType().Name ?? ""}"; - this.logger.Write(LogLevel.Verbose, msg); - psVariable.Value = psobject; - } - - // Use the VariableDetails.ValueString functionality to get the string representation for client debugger. - // This makes the returned string consistent with the strings normally displayed for variables in the debugger. - var tempVariable = new VariableDetails(psVariable); - this.logger.Write(LogLevel.Verbose, $"Set variable '{name}' to: {tempVariable.ValueString ?? ""}"); - return tempVariable.ValueString; - } - - /// - /// Evaluates an expression in the context of the stopped - /// debugger. This method will execute the specified expression - /// PowerShellContext. - /// - /// The expression string to execute. - /// The ID of the stack frame in which the expression should be executed. - /// - /// If true, writes the expression result as host output rather than returning the results. - /// In this case, the return value of this function will be null. - /// A VariableDetails object containing the result. - public async Task EvaluateExpressionAsync( - string expressionString, - int stackFrameId, - bool writeResultAsOutput) - { - var results = - await this.powerShellContext.ExecuteScriptStringAsync( - expressionString, - false, - writeResultAsOutput); - - // Since this method should only be getting invoked in the debugger, - // we can assume that Out-String will be getting used to format results - // of command executions into string output. However, if null is returned - // then return null so that no output gets displayed. - string outputString = - results != null && results.Any() ? - string.Join(Environment.NewLine, results) : - null; - - // If we've written the result as output, don't return a - // VariableDetails instance. - return - writeResultAsOutput ? - null : - new VariableDetails( - expressionString, - outputString); - } - - /// - /// Gets the list of stack frames at the point where the - /// debugger sf stopped. - /// - /// - /// An array of StackFrameDetails instances that contain the stack trace. - /// - public StackFrameDetails[] GetStackFrames() - { - this.debugInfoHandle.Wait(); - try - { - return this.stackFrameDetails; - } - finally - { - this.debugInfoHandle.Release(); - } - } - - internal StackFrameDetails[] GetStackFrames(CancellationToken cancellationToken) - { - this.debugInfoHandle.Wait(cancellationToken); - try - { - return this.stackFrameDetails; - } - finally - { - this.debugInfoHandle.Release(); - } - } - - internal async Task GetStackFramesAsync() - { - await this.debugInfoHandle.WaitAsync(); - try - { - return this.stackFrameDetails; - } - finally - { - this.debugInfoHandle.Release(); - } - } - - internal async Task GetStackFramesAsync(CancellationToken cancellationToken) - { - await this.debugInfoHandle.WaitAsync(cancellationToken); - try - { - return this.stackFrameDetails; - } - finally - { - this.debugInfoHandle.Release(); - } - } - - /// - /// Gets the list of variable scopes for the stack frame that - /// is identified by the given ID. - /// - /// The ID of the stack frame at which variable scopes should be retrieved. - /// The list of VariableScope instances which describe the available variable scopes. - public VariableScope[] GetVariableScopes(int stackFrameId) - { - var stackFrames = this.GetStackFrames(); - int localStackFrameVariableId = stackFrames[stackFrameId].LocalVariables.Id; - int autoVariablesId = stackFrames[stackFrameId].AutoVariables.Id; - - return new VariableScope[] - { - new VariableScope(autoVariablesId, VariableContainerDetails.AutoVariablesName), - new VariableScope(localStackFrameVariableId, VariableContainerDetails.LocalScopeName), - new VariableScope(this.scriptScopeVariables.Id, VariableContainerDetails.ScriptScopeName), - new VariableScope(this.globalScopeVariables.Id, VariableContainerDetails.GlobalScopeName), - }; - } - - /// - /// Clears all breakpoints in the current session. - /// - public async Task ClearAllBreakpointsAsync() - { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); - - await this.powerShellContext.ExecuteCommandAsync(psCommand); - } - - #endregion - - #region Private Methods - - private async Task ClearBreakpointsInFileAsync(ScriptFile scriptFile) - { - List breakpoints = null; - - // Get the list of breakpoints for this file - if (this.breakpointsPerFile.TryGetValue(scriptFile.Id, out breakpoints)) - { - if (breakpoints.Count > 0) - { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); - psCommand.AddParameter("Id", breakpoints.Select(b => b.Id).ToArray()); - - await this.powerShellContext.ExecuteCommandAsync(psCommand); - - // Clear the existing breakpoints list for the file - breakpoints.Clear(); - } - } - } - - private async Task ClearCommandBreakpointsAsync() - { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - psCommand.AddParameter("Type", "Command"); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); - - await this.powerShellContext.ExecuteCommandAsync(psCommand); - } - - private async Task FetchStackFramesAndVariablesAsync(string scriptNameOverride) - { - await this.debugInfoHandle.WaitAsync(); - try - { - this.nextVariableId = VariableDetailsBase.FirstVariableId; - this.variables = new List(); - - // Create a dummy variable for index 0, should never see this. - this.variables.Add(new VariableDetails("Dummy", null)); - - // Must retrieve global/script variales before stack frame variables - // as we check stack frame variables against globals. - await FetchGlobalAndScriptVariablesAsync(); - await FetchStackFramesAsync(scriptNameOverride); - } - finally - { - this.debugInfoHandle.Release(); - } - } - - private async Task FetchGlobalAndScriptVariablesAsync() - { - // Retrieve globals first as script variable retrieval needs to search globals. - this.globalScopeVariables = - await FetchVariableContainerAsync(VariableContainerDetails.GlobalScopeName, null); - - this.scriptScopeVariables = - await FetchVariableContainerAsync(VariableContainerDetails.ScriptScopeName, null); - } - - private async Task FetchVariableContainerAsync( - string scope, - VariableContainerDetails autoVariables) - { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand("Get-Variable"); - psCommand.AddParameter("Scope", scope); - - var scopeVariableContainer = - new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); - this.variables.Add(scopeVariableContainer); - - var results = await this.powerShellContext.ExecuteCommandAsync(psCommand, sendErrorToHost: false); - if (results != null) - { - foreach (PSObject psVariableObject in results) - { - var variableDetails = new VariableDetails(psVariableObject) { Id = this.nextVariableId++ }; - this.variables.Add(variableDetails); - scopeVariableContainer.Children.Add(variableDetails.Name, variableDetails); - - if ((autoVariables != null) && AddToAutoVariables(psVariableObject, scope)) - { - autoVariables.Children.Add(variableDetails.Name, variableDetails); - } - } - } - - return scopeVariableContainer; - } - - private bool AddToAutoVariables(PSObject psvariable, string scope) - { - if ((scope == VariableContainerDetails.GlobalScopeName) || - (scope == VariableContainerDetails.ScriptScopeName)) - { - // We don't A) have a good way of distinguishing built-in from user created variables - // and B) globalScopeVariables.Children.ContainsKey() doesn't work for built-in variables - // stored in a child variable container within the globals variable container. - return false; - } - - string variableName = psvariable.Properties["Name"].Value as string; - object variableValue = psvariable.Properties["Value"].Value; - - // Don't put any variables created by PSES in the Auto variable container. - if (variableName.StartsWith(PsesGlobalVariableNamePrefix) || - variableName.Equals("PSDebugContext")) - { - return false; - } - - ScopedItemOptions variableScope = ScopedItemOptions.None; - PSPropertyInfo optionsProperty = psvariable.Properties["Options"]; - if (string.Equals(optionsProperty.TypeNameOfValue, "System.String")) - { - if (!Enum.TryParse( - optionsProperty.Value as string, - out variableScope)) - { - this.logger.Write( - LogLevel.Warning, - $"Could not parse a variable's ScopedItemOptions value of '{optionsProperty.Value}'"); - } - } - else if (optionsProperty.Value is ScopedItemOptions) - { - variableScope = (ScopedItemOptions)optionsProperty.Value; - } - - // Some local variables, if they exist, should be displayed by default - if (psvariable.TypeNames[0].EndsWith("LocalVariable")) - { - if (variableName.Equals("_")) - { - return true; - } - else if (variableName.Equals("args", StringComparison.OrdinalIgnoreCase)) - { - var array = variableValue as Array; - return array != null ? array.Length > 0 : false; - } - - return false; - } - else if (!psvariable.TypeNames[0].EndsWith(nameof(PSVariable))) - { - return false; - } - - var constantAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.Constant; - var readonlyAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly; - - if (((variableScope & constantAllScope) == constantAllScope) || - ((variableScope & readonlyAllScope) == readonlyAllScope)) - { - string prefixedVariableName = VariableDetails.DollarPrefix + variableName; - if (this.globalScopeVariables.Children.ContainsKey(prefixedVariableName)) - { - return false; - } - } - - return true; - } - - private async Task FetchStackFramesAsync(string scriptNameOverride) - { - PSCommand psCommand = new PSCommand(); - - // This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame - // objects (or "deserialized" CallStackFrames) when attached to a runspace in another - // process. Without the intermediate variable Get-PSCallStack inexplicably returns - // an array of strings containing the formatted output of the CallStackFrame list. - var callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; - psCommand.AddScript($"{callStackVarName} = Get-PSCallStack; {callStackVarName}"); - - var results = await this.powerShellContext.ExecuteCommandAsync(psCommand); - - var callStackFrames = results.ToArray(); - - this.stackFrameDetails = new StackFrameDetails[callStackFrames.Length]; - - for (int i = 0; i < callStackFrames.Length; i++) - { - VariableContainerDetails autoVariables = - new VariableContainerDetails( - this.nextVariableId++, - VariableContainerDetails.AutoVariablesName); - - this.variables.Add(autoVariables); - - VariableContainerDetails localVariables = - await FetchVariableContainerAsync(i.ToString(), autoVariables); - - // When debugging, this is the best way I can find to get what is likely the workspace root. - // This is controlled by the "cwd:" setting in the launch config. - string workspaceRootPath = this.powerShellContext.InitialWorkingDirectory; - - this.stackFrameDetails[i] = - StackFrameDetails.Create(callStackFrames[i], autoVariables, localVariables, workspaceRootPath); - - string stackFrameScriptPath = this.stackFrameDetails[i].ScriptPath; - if (scriptNameOverride != null && - string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath)) - { - this.stackFrameDetails[i].ScriptPath = scriptNameOverride; - } - else if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null && - !string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath)) - { - this.stackFrameDetails[i].ScriptPath = - this.remoteFileManager.GetMappedPath( - stackFrameScriptPath, - this.powerShellContext.CurrentRunspace); - } - } - } - - /// - /// Inspects the condition, putting in the appropriate scriptblock template - /// "if (expression) { break }". If errors are found in the condition, the - /// breakpoint passed in is updated to set Verified to false and an error - /// message is put into the breakpoint.Message property. - /// - /// - /// - private ScriptBlock GetBreakpointActionScriptBlock( - BreakpointDetailsBase breakpoint) - { - try - { - ScriptBlock actionScriptBlock; - int? hitCount = null; - - // If HitCondition specified, parse and verify it. - if (!(String.IsNullOrWhiteSpace(breakpoint.HitCondition))) - { - int parsedHitCount; - - if (Int32.TryParse(breakpoint.HitCondition, out parsedHitCount)) - { - hitCount = parsedHitCount; - } - else - { - breakpoint.Verified = false; - breakpoint.Message = $"The specified HitCount '{breakpoint.HitCondition}' is not valid. " + - "The HitCount must be an integer number."; - return null; - } - } - - // Create an Action scriptblock based on condition and/or hit count passed in. - if (hitCount.HasValue && String.IsNullOrWhiteSpace(breakpoint.Condition)) - { - // In the HitCount only case, this is simple as we can just use the HitCount - // property on the breakpoint object which is represented by $_. - string action = $"if ($_.HitCount -eq {hitCount}) {{ break }}"; - actionScriptBlock = ScriptBlock.Create(action); - } - else if (!String.IsNullOrWhiteSpace(breakpoint.Condition)) - { - // Must be either condition only OR condition and hit count. - actionScriptBlock = ScriptBlock.Create(breakpoint.Condition); - - // Check for simple, common errors that ScriptBlock parsing will not catch - // e.g. $i == 3 and $i > 3 - string message; - if (!ValidateBreakpointConditionAst(actionScriptBlock.Ast, out message)) - { - breakpoint.Verified = false; - breakpoint.Message = message; - return null; - } - - // Check for "advanced" condition syntax i.e. if the user has specified - // a "break" or "continue" statement anywhere in their scriptblock, - // pass their scriptblock through to the Action parameter as-is. - Ast breakOrContinueStatementAst = - actionScriptBlock.Ast.Find( - ast => (ast is BreakStatementAst || ast is ContinueStatementAst), true); - - // If this isn't advanced syntax then the conditions string should be a simple - // expression that needs to be wrapped in a "if" test that conditionally executes - // a break statement. - if (breakOrContinueStatementAst == null) - { - string wrappedCondition; - - if (hitCount.HasValue) - { - string globalHitCountVarName = - $"$global:{PsesGlobalVariableNamePrefix}BreakHitCounter_{breakpointHitCounter++}"; - - wrappedCondition = - $"if ({breakpoint.Condition}) {{ if (++{globalHitCountVarName} -eq {hitCount}) {{ break }} }}"; - } - else - { - wrappedCondition = $"if ({breakpoint.Condition}) {{ break }}"; - } - - actionScriptBlock = ScriptBlock.Create(wrappedCondition); - } - } - else - { - // Shouldn't get here unless someone called this with no condition and no hit count. - actionScriptBlock = ScriptBlock.Create("break"); - this.logger.Write(LogLevel.Warning, "No condition and no hit count specified by caller."); - } - - return actionScriptBlock; - } - catch (ParseException ex) - { - // Failed to create conditional breakpoint likely because the user provided an - // invalid PowerShell expression. Let the user know why. - breakpoint.Verified = false; - breakpoint.Message = ExtractAndScrubParseExceptionMessage(ex, breakpoint.Condition); - return null; - } - } - - private bool ValidateBreakpointConditionAst(Ast conditionAst, out string message) - { - message = string.Empty; - - // We are only inspecting a few simple scenarios in the EndBlock only. - ScriptBlockAst scriptBlockAst = conditionAst as ScriptBlockAst; - if ((scriptBlockAst != null) && - (scriptBlockAst.BeginBlock == null) && - (scriptBlockAst.ProcessBlock == null) && - (scriptBlockAst.EndBlock != null) && - (scriptBlockAst.EndBlock.Statements.Count == 1)) - { - StatementAst statementAst = scriptBlockAst.EndBlock.Statements[0]; - string condition = statementAst.Extent.Text; - - if (statementAst is AssignmentStatementAst) - { - message = FormatInvalidBreakpointConditionMessage(condition, "Use '-eq' instead of '=='."); - return false; - } - - PipelineAst pipelineAst = statementAst as PipelineAst; - if ((pipelineAst != null) && (pipelineAst.PipelineElements.Count == 1) && - (pipelineAst.PipelineElements[0].Redirections.Count > 0)) - { - message = FormatInvalidBreakpointConditionMessage(condition, "Use '-gt' instead of '>'."); - return false; - } - } - - return true; - } - - private string ExtractAndScrubParseExceptionMessage(ParseException parseException, string condition) - { - string[] messageLines = parseException.Message.Split('\n'); - - // Skip first line - it is a location indicator "At line:1 char: 4" - for (int i = 1; i < messageLines.Length; i++) - { - string line = messageLines[i]; - if (line.StartsWith("+")) - { - continue; - } - - if (!string.IsNullOrWhiteSpace(line)) - { - // Note '==' and '>" do not generate parse errors - if (line.Contains("'!='")) - { - line += " Use operator '-ne' instead of '!='."; - } - else if (line.Contains("'<'") && condition.Contains("<=")) - { - line += " Use operator '-le' instead of '<='."; - } - else if (line.Contains("'<'")) - { - line += " Use operator '-lt' instead of '<'."; - } - else if (condition.Contains(">=")) - { - line += " Use operator '-ge' instead of '>='."; - } - - return FormatInvalidBreakpointConditionMessage(condition, line); - } - } - - // If the message format isn't in a form we expect, just return the whole message. - return FormatInvalidBreakpointConditionMessage(condition, parseException.Message); - } - - private string FormatInvalidBreakpointConditionMessage(string condition, string message) - { - return $"'{condition}' is not a valid PowerShell expression. {message}"; - } - - private string TrimScriptListingLine(PSObject scriptLineObj, ref int prefixLength) - { - string scriptLine = scriptLineObj.ToString(); - - if (!string.IsNullOrWhiteSpace(scriptLine)) - { - if (prefixLength == 0) - { - // The prefix is a padded integer ending with ':', an asterisk '*' - // if this is the current line, and one character of padding - prefixLength = scriptLine.IndexOf(':') + 2; - } - - return scriptLine.Substring(prefixLength); - } - - return null; - } - - #endregion - - #region Events - - /// - /// Raised when the debugger stops execution at a breakpoint or when paused. - /// - public event EventHandler DebuggerStopped; - - private async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e) - { - bool noScriptName = false; - string localScriptPath = e.InvocationInfo.ScriptName; - - // If there's no ScriptName, get the "list" of the current source - if (this.remoteFileManager != null && string.IsNullOrEmpty(localScriptPath)) - { - // Get the current script listing and create the buffer - PSCommand command = new PSCommand(); - command.AddScript($"list 1 {int.MaxValue}"); - - IEnumerable scriptListingLines = - await this.powerShellContext.ExecuteCommandAsync( - command, false, false); - - if (scriptListingLines != null) - { - int linePrefixLength = 0; - - string scriptListing = - string.Join( - Environment.NewLine, - scriptListingLines - .Select(o => this.TrimScriptListingLine(o, ref linePrefixLength)) - .Where(s => s != null)); - - this.temporaryScriptListingPath = - this.remoteFileManager.CreateTemporaryFile( - $"[{this.powerShellContext.CurrentRunspace.SessionDetails.ComputerName}] {TemporaryScriptFileName}", - scriptListing, - this.powerShellContext.CurrentRunspace); - - localScriptPath = - this.temporaryScriptListingPath - ?? StackFrameDetails.NoFileScriptPath; - - noScriptName = localScriptPath != null; - } - else - { - this.logger.Write( - LogLevel.Warning, - $"Could not load script context"); - } - } - - // Get call stack and variables. - await this.FetchStackFramesAndVariablesAsync( - noScriptName ? localScriptPath : null); - - // If this is a remote connection and the debugger stopped at a line - // in a script file, get the file contents - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null && - !noScriptName) - { - localScriptPath = - await this.remoteFileManager.FetchRemoteFileAsync( - e.InvocationInfo.ScriptName, - this.powerShellContext.CurrentRunspace); - } - - if (this.stackFrameDetails.Length > 0) - { - // Augment the top stack frame with details from the stop event - IScriptExtent scriptExtent = - this.invocationTypeScriptPositionProperty - .GetValue(e.InvocationInfo) as IScriptExtent; - - if (scriptExtent != null) - { - this.stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber; - this.stackFrameDetails[0].EndLineNumber = scriptExtent.EndLineNumber; - this.stackFrameDetails[0].StartColumnNumber = scriptExtent.StartColumnNumber; - this.stackFrameDetails[0].EndColumnNumber = scriptExtent.EndColumnNumber; - } - } - - this.CurrentDebuggerStoppedEventArgs = - new DebuggerStoppedEventArgs( - e, - this.powerShellContext.CurrentRunspace, - localScriptPath); - - // Notify the host that the debugger is stopped - this.DebuggerStopped?.Invoke( - sender, - this.CurrentDebuggerStoppedEventArgs); - } - - private void OnDebuggerResumed(object sender, DebuggerResumeAction e) - { - this.CurrentDebuggerStoppedEventArgs = null; - } - - /// - /// Raised when a breakpoint is added/removed/updated in the debugger. - /// - public event EventHandler BreakpointUpdated; - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) - { - // This event callback also gets called when a CommandBreakpoint is modified. - // Only execute the following code for LineBreakpoint so we can keep track - // of which line breakpoints exist per script file. We use this later when - // we need to clear all breakpoints in a script file. We do not need to do - // this for CommandBreakpoint, as those span all script files. - LineBreakpoint lineBreakpoint = e.Breakpoint as LineBreakpoint; - if (lineBreakpoint != null) - { - List breakpoints; - - string scriptPath = lineBreakpoint.Script; - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null) - { - string mappedPath = - this.remoteFileManager.GetMappedPath( - scriptPath, - this.powerShellContext.CurrentRunspace); - - if (mappedPath == null) - { - this.logger.Write( - LogLevel.Error, - $"Could not map remote path '{scriptPath}' to a local path."); - - return; - } - - scriptPath = mappedPath; - } - - // Normalize the script filename for proper indexing - string normalizedScriptName = scriptPath.ToLower(); - - // Get the list of breakpoints for this file - if (!this.breakpointsPerFile.TryGetValue(normalizedScriptName, out breakpoints)) - { - breakpoints = new List(); - this.breakpointsPerFile.Add( - normalizedScriptName, - breakpoints); - } - - // Add or remove the breakpoint based on the update type - if (e.UpdateType == BreakpointUpdateType.Set) - { - breakpoints.Add(e.Breakpoint); - } - else if (e.UpdateType == BreakpointUpdateType.Removed) - { - breakpoints.Remove(e.Breakpoint); - } - else - { - // TODO: Do I need to switch out instances for updated breakpoints? - } - } - - this.BreakpointUpdated?.Invoke(sender, e); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Debugging/DebuggerStoppedEventArgs.cs b/src/PowerShellEditorServices/Debugging/DebuggerStoppedEventArgs.cs deleted file mode 100644 index f53ab8323..000000000 --- a/src/PowerShellEditorServices/Debugging/DebuggerStoppedEventArgs.cs +++ /dev/null @@ -1,117 +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 Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Utility; -using System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Debugging -{ - /// - /// Provides event arguments for the DebugService.DebuggerStopped event. - /// - public class DebuggerStoppedEventArgs - { - #region Properties - - /// - /// Gets the path of the script where the debugger has stopped execution. - /// If 'IsRemoteSession' returns true, this path will be a local filesystem - /// path containing the contents of the script that is executing remotely. - /// - public string ScriptPath { get; private set; } - - /// - /// Returns true if the breakpoint was raised from a remote debugging session. - /// - public bool IsRemoteSession - { - get { return this.RunspaceDetails.Location == RunspaceLocation.Remote; } - } - - /// - /// Gets the original script path if 'IsRemoteSession' returns true. - /// - public string RemoteScriptPath { get; private set; } - - /// - /// Gets the RunspaceDetails for the current runspace. - /// - public RunspaceDetails RunspaceDetails { get; private set; } - - /// - /// Gets the line number at which the debugger stopped execution. - /// - public int LineNumber - { - get - { - return this.OriginalEvent.InvocationInfo.ScriptLineNumber; - } - } - - /// - /// Gets the column number at which the debugger stopped execution. - /// - public int ColumnNumber - { - get - { - return this.OriginalEvent.InvocationInfo.OffsetInLine; - } - } - - /// - /// Gets the original DebuggerStopEventArgs from the PowerShell engine. - /// - public DebuggerStopEventArgs OriginalEvent { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the DebuggerStoppedEventArgs class. - /// - /// The original DebuggerStopEventArgs instance from which this instance is based. - /// The RunspaceDetails of the runspace which raised this event. - public DebuggerStoppedEventArgs( - DebuggerStopEventArgs originalEvent, - RunspaceDetails runspaceDetails) - : this(originalEvent, runspaceDetails, null) - { - } - - /// - /// Creates a new instance of the DebuggerStoppedEventArgs class. - /// - /// The original DebuggerStopEventArgs instance from which this instance is based. - /// The RunspaceDetails of the runspace which raised this event. - /// The local path of the remote script being debugged. - public DebuggerStoppedEventArgs( - DebuggerStopEventArgs originalEvent, - RunspaceDetails runspaceDetails, - string localScriptPath) - { - Validate.IsNotNull(nameof(originalEvent), originalEvent); - Validate.IsNotNull(nameof(runspaceDetails), runspaceDetails); - - if (!string.IsNullOrEmpty(localScriptPath)) - { - this.ScriptPath = localScriptPath; - this.RemoteScriptPath = originalEvent.InvocationInfo.ScriptName; - } - else - { - this.ScriptPath = originalEvent.InvocationInfo.ScriptName; - } - - this.OriginalEvent = originalEvent; - this.RunspaceDetails = runspaceDetails; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Debugging/InvalidPowerShellExpressionException.cs b/src/PowerShellEditorServices/Debugging/InvalidPowerShellExpressionException.cs deleted file mode 100644 index fd6b13caf..000000000 --- a/src/PowerShellEditorServices/Debugging/InvalidPowerShellExpressionException.cs +++ /dev/null @@ -1,24 +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.Debugging -{ - /// - /// Represents the exception that is thrown when an invalid expression is provided to the DebugService's SetVariable method. - /// - public class InvalidPowerShellExpressionException : Exception - { - /// - /// Initializes a new instance of the SetVariableExpressionException class. - /// - /// Message indicating why the expression is invalid. - public InvalidPowerShellExpressionException(string message) - : base(message) - { - } - } -} diff --git a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs deleted file mode 100644 index e217457fc..000000000 --- a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs +++ /dev/null @@ -1,135 +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.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Contains details pertaining to a single stack frame in - /// the current debugging session. - /// - public class StackFrameDetails - { - #region Fields - - /// - /// A constant string used in the ScriptPath field to represent a - /// stack frame with no associated script file. - /// - public const string NoFileScriptPath = ""; - - #endregion - - #region Properties - - /// - /// Gets the path to the script where the stack frame occurred. - /// - public string ScriptPath { get; internal set; } - - /// - /// Gets the name of the function where the stack frame occurred. - /// - public string FunctionName { get; private set; } - - /// - /// Gets the start line number of the script where the stack frame occurred. - /// - public int StartLineNumber { get; internal set; } - - /// - /// Gets the line number of the script where the stack frame occurred. - /// - public int? EndLineNumber { get; internal set; } - - /// - /// Gets the start column number of the line where the stack frame occurred. - /// - public int StartColumnNumber { get; internal set; } - - /// - /// Gets the end column number of the line where the stack frame occurred. - /// - public int? EndColumnNumber { get; internal set; } - - /// - /// Gets a boolean value indicating whether or not the stack frame is executing - /// in script external to the current workspace root. - /// - public bool IsExternalCode { get; internal set; } - - /// - /// Gets or sets the VariableContainerDetails that contains the auto variables. - /// - public VariableContainerDetails AutoVariables { get; private set; } - - /// - /// Gets or sets the VariableContainerDetails that contains the local variables. - /// - public VariableContainerDetails LocalVariables { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the StackFrameDetails class from a - /// CallStackFrame instance provided by the PowerShell engine. - /// - /// - /// A PSObject representing the CallStackFrame instance from which details will be obtained. - /// - /// - /// A variable container with all the filtered, auto variables for this stack frame. - /// - /// - /// A variable container with all the local variables for this stack frame. - /// - /// - /// Specifies the path to the root of an open workspace, if one is open. This path is used to - /// determine whether individua stack frames are external to the workspace. - /// - /// A new instance of the StackFrameDetails class. - static internal StackFrameDetails Create( - PSObject callStackFrameObject, - VariableContainerDetails autoVariables, - VariableContainerDetails localVariables, - string workspaceRootPath = null) - { - string moduleId = string.Empty; - var isExternal = false; - - var invocationInfo = callStackFrameObject.Properties["InvocationInfo"]?.Value as InvocationInfo; - string scriptPath = (callStackFrameObject.Properties["ScriptName"].Value as string) ?? NoFileScriptPath; - int startLineNumber = (int)(callStackFrameObject.Properties["ScriptLineNumber"].Value ?? 0); - - // TODO: RKH 2019-03-07 Temporarily disable "external" code until I have a chance to add - // settings to control this feature. - //if (workspaceRootPath != null && - // invocationInfo != null && - // !scriptPath.StartsWith(workspaceRootPath, StringComparison.OrdinalIgnoreCase)) - //{ - // isExternal = true; - //} - - return new StackFrameDetails - { - ScriptPath = scriptPath, - FunctionName = callStackFrameObject.Properties["FunctionName"].Value as string, - StartLineNumber = startLineNumber, - EndLineNumber = startLineNumber, // End line number isn't given in PowerShell stack frames - StartColumnNumber = 0, // Column number isn't given in PowerShell stack frames - EndColumnNumber = 0, - AutoVariables = autoVariables, - LocalVariables = localVariables, - IsExternalCode = isExternal - }; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs deleted file mode 100644 index 2c90d8891..000000000 --- a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs +++ /dev/null @@ -1,99 +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.Collections.Generic; -using System.Diagnostics; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Container for variables that is not itself a variable per se. However given how - /// VSCode uses an integer variable reference id for every node under the "Variables" tool - /// window, it is useful to treat containers, typically scope containers, as a variable. - /// Note that these containers are not necessarily always a scope container. Consider a - /// container such as "Auto" or "My". These aren't scope related but serve as just another - /// way to organize variables into a useful UI structure. - /// - [DebuggerDisplay("Name = {Name}, Id = {Id}, Count = {Children.Count}")] - public class VariableContainerDetails : VariableDetailsBase - { - /// - /// Provides a constant for the name of the Global scope. - /// - public const string AutoVariablesName = "Auto"; - - /// - /// Provides a constant for the name of the Global scope. - /// - public const string GlobalScopeName = "Global"; - - /// - /// Provides a constant for the name of the Local scope. - /// - public const string LocalScopeName = "Local"; - - /// - /// Provides a constant for the name of the Script scope. - /// - public const string ScriptScopeName = "Script"; - - private readonly Dictionary children; - - /// - /// Instantiates an instance of VariableScopeDetails. - /// - /// The variable reference id for this scope. - /// The name of the variable scope. - public VariableContainerDetails(int id, string name) - { - Validate.IsNotNull(name, "name"); - - this.Id = id; - this.Name = name; - this.IsExpandable = true; - this.ValueString = " "; // An empty string isn't enough due to a temporary bug in VS Code. - - this.children = new Dictionary(); - } - - /// - /// Gets the collection of child variables. - /// - public IDictionary Children - { - get { return this.children; } - } - - /// - /// Returns the details of the variable container's children. If empty, returns an empty array. - /// - /// - public override VariableDetailsBase[] GetChildren(ILogger logger) - { - var variablesArray = new VariableDetailsBase[this.children.Count]; - this.children.Values.CopyTo(variablesArray, 0); - return variablesArray; - } - - /// - /// Determines whether this variable container contains the specified variable by its referenceId. - /// - /// The variableReferenceId to search for. - /// Returns true if this variable container directly contains the specified variableReferenceId, false otherwise. - public bool ContainsVariable(int variableReferenceId) - { - foreach (VariableDetailsBase value in this.children.Values) - { - if (value.Id == variableReferenceId) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/PowerShellEditorServices/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Debugging/VariableDetails.cs deleted file mode 100644 index 25ee71057..000000000 --- a/src/PowerShellEditorServices/Debugging/VariableDetails.cs +++ /dev/null @@ -1,415 +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; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Management.Automation; -using System.Reflection; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Contains details pertaining to a variable in the current - /// debugging session. - /// - [DebuggerDisplay("Name = {Name}, Id = {Id}, Value = {ValueString}")] - public class VariableDetails : VariableDetailsBase - { - #region Fields - - /// - /// Provides a constant for the dollar sign variable prefix string. - /// - public const string DollarPrefix = "$"; - - private object valueObject; - private VariableDetails[] cachedChildren; - - #endregion - - #region Constructors - - /// - /// Initializes an instance of the VariableDetails class from - /// the details contained in a PSVariable instance. - /// - /// - /// The PSVariable instance from which variable details will be obtained. - /// - public VariableDetails(PSVariable psVariable) - : this(DollarPrefix + psVariable.Name, psVariable.Value) - { - } - - /// - /// Initializes an instance of the VariableDetails class from - /// the name and value pair stored inside of a PSObject which - /// represents a PSVariable. - /// - /// - /// The PSObject which represents a PSVariable. - /// - public VariableDetails(PSObject psVariableObject) - : this( - DollarPrefix + psVariableObject.Properties["Name"].Value as string, - psVariableObject.Properties["Value"].Value) - { - } - - /// - /// Initializes an instance of the VariableDetails class from - /// the details contained in a PSPropertyInfo instance. - /// - /// - /// The PSPropertyInfo instance from which variable details will be obtained. - /// - public VariableDetails(PSPropertyInfo psProperty) - : this(psProperty.Name, psProperty.Value) - { - } - - /// - /// Initializes an instance of the VariableDetails class from - /// a given name/value pair. - /// - /// The variable's name. - /// The variable's value. - public VariableDetails(string name, object value) - { - this.valueObject = value; - - this.Id = -1; // Not been assigned a variable reference id yet - this.Name = name; - this.IsExpandable = GetIsExpandable(value); - - string typeName; - this.ValueString = GetValueStringAndType(value, this.IsExpandable, out typeName); - this.Type = typeName; - } - - #endregion - - #region Public Methods - - /// - /// If this variable instance is expandable, this method returns the - /// details of its children. Otherwise it returns an empty array. - /// - /// - public override VariableDetailsBase[] GetChildren(ILogger logger) - { - VariableDetails[] childVariables = null; - - if (this.IsExpandable) - { - if (this.cachedChildren == null) - { - this.cachedChildren = GetChildren(this.valueObject, logger); - } - - return this.cachedChildren; - } - else - { - childVariables = new VariableDetails[0]; - } - - return childVariables; - } - - #endregion - - #region Private Methods - - private static bool GetIsExpandable(object valueObject) - { - if (valueObject == null) - { - return false; - } - - // If a PSObject, unwrap it - var psobject = valueObject as PSObject; - if (psobject != null) - { - valueObject = psobject.BaseObject; - } - - Type valueType = - valueObject != null ? - valueObject.GetType() : - null; - - TypeInfo valueTypeInfo = valueType.GetTypeInfo(); - - return - valueObject != null && - !valueTypeInfo.IsPrimitive && - !valueTypeInfo.IsEnum && // Enums don't have any properties - !(valueObject is string) && // Strings get treated as IEnumerables - !(valueObject is decimal) && - !(valueObject is UnableToRetrievePropertyMessage); - } - - private static string GetValueStringAndType(object value, bool isExpandable, out string typeName) - { - string valueString = null; - typeName = null; - - if (value == null) - { - // Set to identifier recognized by PowerShell to make setVariable from the debug UI more natural. - return "$null"; - } - - Type objType = value.GetType(); - typeName = $"[{objType.FullName}]"; - - if (value is bool) - { - // Set to identifier recognized by PowerShell to make setVariable from the debug UI more natural. - valueString = (bool) value ? "$true" : "$false"; - } - else if (isExpandable) - { - - // Get the "value" for an expandable object. - if (value is DictionaryEntry) - { - // For DictionaryEntry - display the key/value as the value. - var entry = (DictionaryEntry)value; - valueString = - string.Format( - "[{0}, {1}]", - entry.Key, - GetValueStringAndType(entry.Value, GetIsExpandable(entry.Value), out typeName)); - } - else - { - string valueToString = value.SafeToString(); - if (valueToString.Equals(objType.ToString())) - { - // If the ToString() matches the type name, then display the type - // name in PowerShell format. - string shortTypeName = objType.Name; - - // For arrays and ICollection, display the number of contained items. - if (value is Array) - { - var arr = value as Array; - if (arr.Rank == 1) - { - shortTypeName = InsertDimensionSize(shortTypeName, arr.Length); - } - } - else if (value is ICollection) - { - var collection = (ICollection)value; - shortTypeName = InsertDimensionSize(shortTypeName, collection.Count); - } - - valueString = $"[{shortTypeName}]"; - } - else - { - valueString = valueToString; - } - } - } - else - { - // Value is a scalar (not expandable). If it's a string, display it directly otherwise use SafeToString() - if (value is string) - { - valueString = "\"" + value + "\""; - } - else - { - valueString = value.SafeToString(); - } - } - - return valueString; - } - - private static string InsertDimensionSize(string value, int dimensionSize) - { - string result = value; - - int indexLastRBracket = value.LastIndexOf("]"); - if (indexLastRBracket > 0) - { - result = - value.Substring(0, indexLastRBracket) + - dimensionSize + - value.Substring(indexLastRBracket); - } - else - { - // Types like ArrayList don't use [] in type name so - // display value like so - [ArrayList: 5] - result = value + ": " + dimensionSize; - } - - return result; - } - - private VariableDetails[] GetChildren(object obj, ILogger logger) - { - List childVariables = new List(); - - if (obj == null) - { - return childVariables.ToArray(); - } - - try - { - PSObject psObject = obj as PSObject; - - if ((psObject != null) && - (psObject.TypeNames[0] == typeof(PSCustomObject).ToString())) - { - // PowerShell PSCustomObject's properties are completely defined by the ETS type system. - childVariables.AddRange( - psObject - .Properties - .Select(p => new VariableDetails(p))); - } - else - { - // If a PSObject other than a PSCustomObject, unwrap it. - if (psObject != null) - { - // First add the PSObject's ETS propeties - childVariables.AddRange( - psObject - .Properties - .Where(p => p.MemberType == PSMemberTypes.NoteProperty) - .Select(p => new VariableDetails(p))); - - obj = psObject.BaseObject; - } - - IDictionary dictionary = obj as IDictionary; - IEnumerable enumerable = obj as IEnumerable; - - // We're in the realm of regular, unwrapped .NET objects - if (dictionary != null) - { - // Buckle up kids, this is a bit weird. We could not use the LINQ - // operator OfType. Even though R# will squiggle the - // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT! - // The reason is that LINQ extension methods work with objects of type - // IEnumerable. Objects of type Dictionary<,>, respond to iteration via - // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic - // dictionaries like HashTable return DictionaryEntry objects. - // It turns out that iteration via C#'s foreach loop, operates on the variable's - // type which in this case is IDictionary. IDictionary was designed to always - // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation - // honors that when the object is reintepreted as an IDictionary object. - // FYI, a test case for this is to open $PSBoundParameters when debugging a - // function that defines parameters and has been passed parameters. - // If you open the $PSBoundParameters variable node in this scenario and see nothing, - // this code is broken. - int i = 0; - foreach (DictionaryEntry entry in dictionary) - { - childVariables.Add( - new VariableDetails( - "[" + i++ + "]", - entry)); - } - } - else if (enumerable != null && !(obj is string)) - { - int i = 0; - foreach (var item in enumerable) - { - childVariables.Add( - new VariableDetails( - "[" + i++ + "]", - item)); - } - } - - AddDotNetProperties(obj, childVariables); - } - } - catch (GetValueInvocationException ex) - { - // This exception occurs when accessing the value of a - // variable causes a script to be executed. Right now - // we aren't loading children on the pipeline thread so - // this causes an exception to be raised. In this case, - // just return an empty list of children. - logger.Write(LogLevel.Warning, $"Failed to get properties of variable {this.Name}, value invocation was attempted: {ex.Message}"); - } - - return childVariables.ToArray(); - } - - private static void AddDotNetProperties(object obj, List childVariables) - { - Type objectType = obj.GetType(); - var properties = - objectType.GetProperties( - BindingFlags.Public | BindingFlags.Instance); - - foreach (var property in properties) - { - // Don't display indexer properties, it causes an exception anyway. - if (property.GetIndexParameters().Length > 0) - { - continue; - } - - try - { - childVariables.Add( - new VariableDetails( - property.Name, - property.GetValue(obj))); - } - catch (Exception ex) - { - // Some properties can throw exceptions, add the property - // name and info about the error. - if (ex.GetType() == typeof (TargetInvocationException)) - { - ex = ex.InnerException; - } - - childVariables.Add( - new VariableDetails( - property.Name, - new UnableToRetrievePropertyMessage( - "Error retrieving property - " + ex.GetType().Name))); - } - } - } - - #endregion - - private struct UnableToRetrievePropertyMessage - { - public UnableToRetrievePropertyMessage(string message) - { - this.Message = message; - } - - public string Message { get; } - - public override string ToString() - { - return "<" + Message + ">"; - } - } - } -} diff --git a/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs b/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs deleted file mode 100644 index aada558fa..000000000 --- a/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs +++ /dev/null @@ -1,59 +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 Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Defines the common details between a variable and a variable container such as a scope - /// in the current debugging session. - /// - public abstract class VariableDetailsBase - { - /// - /// Provides a constant that is used as the starting variable ID for all. - /// Avoid 0 as it indicates a variable node with no children. - /// variables. - /// - public const int FirstVariableId = 1; - - /// - /// Gets the numeric ID of the variable which can be used to refer - /// to it in future requests. - /// - public int Id { get; set; } - - /// - /// Gets the variable's name. - /// - public string Name { get; protected set; } - - /// - /// Gets the string representation of the variable's value. - /// If the variable is an expandable object, this string - /// will be empty. - /// - public string ValueString { get; protected set; } - - /// - /// Gets the type of the variable's value. - /// - public string Type { get; protected set; } - - /// - /// Returns true if the variable's value is expandable, meaning - /// that it has child properties or its contents can be enumerated. - /// - public bool IsExpandable { get; protected set; } - - /// - /// If this variable instance is expandable, this method returns the - /// details of its children. Otherwise it returns an empty array. - /// - /// - public abstract VariableDetailsBase[] GetChildren(ILogger logger); - } -} diff --git a/src/PowerShellEditorServices/Debugging/VariableScope.cs b/src/PowerShellEditorServices/Debugging/VariableScope.cs deleted file mode 100644 index 53c98058b..000000000 --- a/src/PowerShellEditorServices/Debugging/VariableScope.cs +++ /dev/null @@ -1,37 +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 -{ - /// - /// Contains details pertaining to a variable scope in the current - /// debugging session. - /// - public class VariableScope - { - /// - /// Gets a numeric ID that can be used in future operations - /// relating to this scope. - /// - public int Id { get; private set; } - - /// - /// Gets a name that describes the variable scope. - /// - public string Name { get; private set; } - - /// - /// Initializes a new instance of the VariableScope class with - /// the given ID and name. - /// - /// The variable scope's ID. - /// The variable scope's name. - public VariableScope(int id, string name) - { - this.Id = id; - this.Name = name; - } - } -} diff --git a/src/PowerShellEditorServices/Extensions/EditorCommand.cs b/src/PowerShellEditorServices/Extensions/EditorCommand.cs deleted file mode 100644 index 8a7b80cef..000000000 --- a/src/PowerShellEditorServices/Extensions/EditorCommand.cs +++ /dev/null @@ -1,89 +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.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides details about a command that has been registered - /// with the editor. - /// - public class EditorCommand - { - #region Properties - - /// - /// Gets the name which uniquely identifies the command. - /// - public string Name { get; private set; } - - /// - /// Gets the display name for the command. - /// - public string DisplayName { get; private set; } - - /// - /// Gets the boolean which determines whether this command's - /// output should be suppressed. - /// - public bool SuppressOutput { get; private set; } - - /// - /// Gets the ScriptBlock which can be used to execute the command. - /// - public ScriptBlock ScriptBlock { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new EditorCommand instance that invokes a cmdlet or - /// function by name. - /// - /// The unique identifier name for the command. - /// The display name for the command. - /// If true, causes output to be suppressed for this command. - /// The name of the cmdlet or function which will be invoked by this command. - public EditorCommand( - string commandName, - string displayName, - bool suppressOutput, - string cmdletName) - : this( - commandName, - displayName, - suppressOutput, - ScriptBlock.Create( - string.Format( - "param($context) {0} $context", - cmdletName))) - { - } - - /// - /// Creates a new EditorCommand instance that invokes a ScriptBlock. - /// - /// The unique identifier name for the command. - /// The display name for the command. - /// If true, causes output to be suppressed for this command. - /// The ScriptBlock which will be invoked by this command. - public EditorCommand( - string commandName, - string displayName, - bool suppressOutput, - ScriptBlock scriptBlock) - { - this.Name = commandName; - this.DisplayName = displayName; - this.SuppressOutput = suppressOutput; - this.ScriptBlock = scriptBlock; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs b/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs deleted file mode 100644 index 8020cb844..000000000 --- a/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides an attribute that can be used to target PowerShell - /// commands for import as editor commands. - /// - [AttributeUsage(AttributeTargets.Class)] - public class EditorCommandAttribute : Attribute - { - - #region Properties - - /// - /// Gets or sets the name which uniquely identifies the command. - /// - public string Name { get; set; } - - /// - /// Gets or sets the display name for the command. - /// - public string DisplayName { get; set; } - - /// - /// Gets or sets a value indicating whether this command's output - /// should be suppressed. - /// - public bool SuppressOutput { get; set; } - - #endregion - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Extensions/EditorContext.cs b/src/PowerShellEditorServices/Extensions/EditorContext.cs deleted file mode 100644 index c669acd19..000000000 --- a/src/PowerShellEditorServices/Extensions/EditorContext.cs +++ /dev/null @@ -1,117 +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.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides context for the host editor at the time of creation. - /// - public class EditorContext - { - #region Private Fields - - private IEditorOperations editorOperations; - - #endregion - - #region Properties - - /// - /// Gets the FileContext for the active file. - /// - public FileContext CurrentFile { get; private set; } - - /// - /// Gets the BufferRange representing the current selection in the file. - /// - public BufferRange SelectedRange { get; private set; } - - /// - /// Gets the FilePosition representing the current cursor position. - /// - public FilePosition CursorPosition { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the EditorContext class. - /// - /// An IEditorOperations implementation which performs operations in the editor. - /// The ScriptFile that is in the active editor buffer. - /// The position of the user's cursor in the active editor buffer. - /// The range of the user's selection in the active editor buffer. - /// Determines the language of the file.false If it is not specified, then it defaults to "Unknown" - public EditorContext( - IEditorOperations editorOperations, - ScriptFile currentFile, - BufferPosition cursorPosition, - BufferRange selectedRange, - string language = "Unknown") - { - this.editorOperations = editorOperations; - this.CurrentFile = new FileContext(currentFile, this, editorOperations, language); - this.SelectedRange = selectedRange; - this.CursorPosition = new FilePosition(currentFile, cursorPosition); - } - - #endregion - - #region Public Methods - - /// - /// Sets a selection in the host editor's active buffer. - /// - /// The 1-based starting line of the selection. - /// The 1-based starting column of the selection. - /// The 1-based ending line of the selection. - /// The 1-based ending column of the selection. - public void SetSelection( - int startLine, - int startColumn, - int endLine, - int endColumn) - { - this.SetSelection( - new BufferRange( - startLine, startColumn, - endLine, endColumn)); - } - - /// - /// Sets a selection in the host editor's active buffer. - /// - /// The starting position of the selection. - /// The ending position of the selection. - public void SetSelection( - BufferPosition startPosition, - BufferPosition endPosition) - { - this.SetSelection( - new BufferRange( - startPosition, - endPosition)); - } - - /// - /// Sets a selection in the host editor's active buffer. - /// - /// The range of the selection. - public void SetSelection(BufferRange selectionRange) - { - this.editorOperations - .SetSelectionAsync(selectionRange) - .Wait(); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Extensions/EditorObject.cs b/src/PowerShellEditorServices/Extensions/EditorObject.cs deleted file mode 100644 index 0013da684..000000000 --- a/src/PowerShellEditorServices/Extensions/EditorObject.cs +++ /dev/null @@ -1,112 +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 Microsoft.PowerShell.EditorServices.Components; -using System; -using System.Management.Automation; -using System.Reflection; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides the entry point of the extensibility API, inserted into - /// the PowerShell session as the "$psEditor" variable. - /// - public class EditorObject - { - #region Private Fields - - private ExtensionService extensionService; - private IEditorOperations editorOperations; - - #endregion - - #region Properties - - /// - /// Gets the version of PowerShell Editor Services. - /// - public Version EditorServicesVersion - { - get { return this.GetType().GetTypeInfo().Assembly.GetName().Version; } - } - - /// - /// Gets the workspace interface for the editor API. - /// - public EditorWorkspace Workspace { get; private set; } - - /// - /// Gets the window interface for the editor API. - /// - public EditorWindow Window { get; private set; } - - /// - /// Gets the component registry for the session. - /// - /// - public IComponentRegistry Components { get; private set; } - - #endregion - - /// - /// Creates a new instance of the EditorObject class. - /// - /// 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( - ExtensionService extensionService, - IEditorOperations editorOperations, - IComponentRegistry componentRegistry) - { - this.extensionService = extensionService; - this.editorOperations = editorOperations; - this.Components = componentRegistry; - - // Create API area objects - this.Workspace = new EditorWorkspace(this.editorOperations); - this.Window = new EditorWindow(this.editorOperations); - } - - /// - /// Registers a new command in the editor. - /// - /// The EditorCommand to be registered. - /// True if the command is newly registered, false if the command already exists. - public bool RegisterCommand(EditorCommand editorCommand) - { - return this.extensionService.RegisterCommand(editorCommand); - } - - /// - /// Unregisters an existing EditorCommand based on its registered name. - /// - /// The name of the command to be unregistered. - public void UnregisterCommand(string commandName) - { - this.extensionService.UnregisterCommand(commandName); - } - - /// - /// Returns all registered EditorCommands. - /// - /// An Array of all registered EditorCommands. - public EditorCommand[] GetCommands() - { - return this.extensionService.GetCommands(); - } - /// - /// Gets the EditorContext which contains the state of the editor - /// at the time this method is invoked. - /// - /// A instance of the EditorContext class. - public EditorContext GetEditorContext() - { - return this.editorOperations.GetEditorContextAsync().Result; - } - } -} - diff --git a/src/PowerShellEditorServices/Extensions/EditorWindow.cs b/src/PowerShellEditorServices/Extensions/EditorWindow.cs deleted file mode 100644 index 8d241559a..000000000 --- a/src/PowerShellEditorServices/Extensions/EditorWindow.cs +++ /dev/null @@ -1,83 +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.Extensions -{ - /// - /// Provides a PowerShell-facing API which allows scripts to - /// interact with the editor's window. - /// - public class EditorWindow - { - #region Private Fields - - private IEditorOperations editorOperations; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the EditorWindow class. - /// - /// An IEditorOperations implementation which handles operations in the host editor. - internal EditorWindow(IEditorOperations editorOperations) - { - this.editorOperations = editorOperations; - } - - #endregion - - #region Public Methods - - /// - /// Shows an informational message to the user. - /// - /// The message to be shown. - public void ShowInformationMessage(string message) - { - this.editorOperations.ShowInformationMessageAsync(message).Wait(); - } - - /// - /// Shows an error message to the user. - /// - /// The message to be shown. - public void ShowErrorMessage(string message) - { - this.editorOperations.ShowErrorMessageAsync(message).Wait(); - } - - /// - /// Shows a warning message to the user. - /// - /// The message to be shown. - public void ShowWarningMessage(string message) - { - this.editorOperations.ShowWarningMessageAsync(message).Wait(); - } - - /// - /// Sets the status bar message in the editor UI (if applicable). - /// - /// The message to be shown. - public void SetStatusBarMessage(string message) - { - this.editorOperations.SetStatusBarMessageAsync(message, null).Wait(); - } - - /// - /// Sets the status bar message in the editor UI (if applicable). - /// - /// The message to be shown. - /// A timeout in milliseconds for how long the message should remain visible. - public void SetStatusBarMessage(string message, int timeout) - { - this.editorOperations.SetStatusBarMessageAsync(message, timeout).Wait(); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs deleted file mode 100644 index ad679f371..000000000 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ /dev/null @@ -1,76 +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.Extensions -{ - /// - /// Provides a PowerShell-facing API which allows scripts to - /// interact with the editor's workspace. - /// - public class EditorWorkspace - { - #region Private Fields - - private IEditorOperations editorOperations; - - #endregion - - #region Properties - - /// - /// Gets the current workspace path if there is one or null otherwise. - /// - public string Path - { - get { return this.editorOperations.GetWorkspacePath(); } - } - - #endregion - - #region Constructors - - internal EditorWorkspace(IEditorOperations editorOperations) - { - this.editorOperations = editorOperations; - } - - #endregion - - #region Public Methods - - /// - /// Creates a new file in the editor - /// - public void NewFile() - { - this.editorOperations.NewFileAsync().Wait(); - } - - /// - /// Opens a file in the workspace. If the file is already open - /// its buffer will be made active. - /// - /// The path to the file to be opened. - public void OpenFile(string filePath) - { - this.editorOperations.OpenFileAsync(filePath).Wait(); - } - - /// - /// Opens a file in the workspace. If the file is already open - /// its buffer will be made active. - /// You can specify whether the file opens as a preview or as a durable editor. - /// - /// The path to the file to be opened. - /// Determines wether the file is opened as a preview or as a durable editor. - public void OpenFile(string filePath, bool preview) - { - this.editorOperations.OpenFileAsync(filePath, preview).Wait(); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Extensions/ExtensionService.cs b/src/PowerShellEditorServices/Extensions/ExtensionService.cs deleted file mode 100644 index cc4e01af4..000000000 --- a/src/PowerShellEditorServices/Extensions/ExtensionService.cs +++ /dev/null @@ -1,220 +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 Microsoft.PowerShell.EditorServices.Components; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Management.Automation; -using System.Reflection; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides a high-level service which enables PowerShell scripts - /// and modules to extend the behavior of the host editor. - /// - public class ExtensionService - { - #region Fields - - private Dictionary editorCommands = - new Dictionary(); - - #endregion - - #region Properties - - /// - /// Gets the IEditorOperations implementation used to invoke operations - /// in the host editor. - /// - public IEditorOperations EditorOperations { get; private set; } - - /// - /// Gets the EditorObject which exists in the PowerShell session as the - /// '$psEditor' variable. - /// - public EditorObject EditorObject { get; private set; } - - /// - /// Gets the PowerShellContext in which extension code will be executed. - /// - public PowerShellContext PowerShellContext { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ExtensionService which uses the provided - /// PowerShellContext for loading and executing extension code. - /// - /// A PowerShellContext used to execute extension code. - public ExtensionService(PowerShellContext powerShellContext) - { - this.PowerShellContext = powerShellContext; - } - - #endregion - - #region Public Methods - - /// - /// Initializes this ExtensionService using the provided IEditorOperations - /// 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) - { - this.EditorObject = - new EditorObject( - this, - editorOperations, - componentRegistry); - - // Register the editor object in the runspace - PSCommand variableCommand = new PSCommand(); - using (RunspaceHandle handle = await this.PowerShellContext.GetRunspaceHandleAsync()) - { - handle.Runspace.SessionStateProxy.PSVariable.Set( - "psEditor", - this.EditorObject); - } - } - - /// - /// Invokes the specified editor command against the provided EditorContext. - /// - /// The unique name of the command to be invoked. - /// The context in which the command is being invoked. - /// A Task that can be awaited for completion. - public async Task InvokeCommandAsync(string commandName, EditorContext editorContext) - { - EditorCommand editorCommand; - - if (this.editorCommands.TryGetValue(commandName, out editorCommand)) - { - PSCommand executeCommand = new PSCommand(); - executeCommand.AddCommand("Invoke-Command"); - executeCommand.AddParameter("ScriptBlock", editorCommand.ScriptBlock); - executeCommand.AddParameter("ArgumentList", new object[] { editorContext }); - - await this.PowerShellContext.ExecuteCommandAsync( - executeCommand, - !editorCommand.SuppressOutput, - true); - } - else - { - throw new KeyNotFoundException( - string.Format( - "Editor command not found: '{0}'", - commandName)); - } - } - - /// - /// Registers a new EditorCommand with the ExtensionService and - /// causes its details to be sent to the host editor. - /// - /// The details about the editor command to be registered. - /// True if the command is newly registered, false if the command already exists. - public bool RegisterCommand(EditorCommand editorCommand) - { - bool commandExists = - this.editorCommands.ContainsKey( - editorCommand.Name); - - // Add or replace the editor command - this.editorCommands[editorCommand.Name] = editorCommand; - - if (!commandExists) - { - this.OnCommandAdded(editorCommand); - } - else - { - this.OnCommandUpdated(editorCommand); - } - - return !commandExists; - } - - /// - /// Unregisters an existing EditorCommand based on its registered name. - /// - /// The name of the command to be unregistered. - public void UnregisterCommand(string commandName) - { - EditorCommand existingCommand = null; - if (this.editorCommands.TryGetValue(commandName, out existingCommand)) - { - this.editorCommands.Remove(commandName); - this.OnCommandRemoved(existingCommand); - } - else - { - throw new KeyNotFoundException( - string.Format( - "Command '{0}' is not registered", - commandName)); - } - } - - /// - /// Returns all registered EditorCommands. - /// - /// An Array of all registered EditorCommands. - public EditorCommand[] GetCommands() - { - EditorCommand[] commands = new EditorCommand[this.editorCommands.Count]; - this.editorCommands.Values.CopyTo(commands,0); - return commands; - } - #endregion - - #region Events - - /// - /// Raised when a new editor command is added. - /// - public event EventHandler CommandAdded; - - private void OnCommandAdded(EditorCommand command) - { - this.CommandAdded?.Invoke(this, command); - } - - /// - /// Raised when an existing editor command is updated. - /// - public event EventHandler CommandUpdated; - - private void OnCommandUpdated(EditorCommand command) - { - this.CommandUpdated?.Invoke(this, command); - } - - /// - /// Raised when an existing editor command is removed. - /// - public event EventHandler CommandRemoved; - - private void OnCommandRemoved(EditorCommand command) - { - this.CommandRemoved?.Invoke(this, command); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs deleted file mode 100644 index 3fc5ac120..000000000 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ /dev/null @@ -1,281 +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.IO; -using System.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides context for a file that is open in the editor. - /// - public class FileContext - { - #region Private Fields - - internal ScriptFile scriptFile; - private EditorContext editorContext; - private IEditorOperations editorOperations; - - #endregion - - #region Properties - - /// - /// Gets the parsed abstract syntax tree for the file. - /// - public Ast Ast - { - get { return this.scriptFile.ScriptAst; } - } - - /// - /// Gets a BufferRange which represents the entire content - /// range of the file. - /// - public BufferRange FileRange - { - get { return this.scriptFile.FileRange; } - } - - /// - /// Gets the language of the file. - /// - public string Language { get; private set; } - - /// - /// Gets the filesystem path of the file. - /// - public string Path - { - get { return this.scriptFile.FilePath; } - } - - /// - /// Gets the parsed token list for the file. - /// - public Token[] Tokens - { - get { return this.scriptFile.ScriptTokens; } - } - - /// - /// Gets the workspace-relative path of the file. - /// - public string WorkspacePath - { - get - { - return - this.editorOperations.GetWorkspaceRelativePath( - this.scriptFile.FilePath); - } - } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the FileContext class. - /// - /// The ScriptFile to which this file refers. - /// The EditorContext to which this file relates. - /// An IEditorOperations implementation which performs operations in the editor. - /// Determines the language of the file.false If it is not specified, then it defaults to "Unknown" - public FileContext( - ScriptFile scriptFile, - EditorContext editorContext, - IEditorOperations editorOperations, - string language = "Unknown") - { - if (string.IsNullOrWhiteSpace(language)) - { - language = "Unknown"; - } - - this.scriptFile = scriptFile; - this.editorContext = editorContext; - this.editorOperations = editorOperations; - this.Language = language; - } - - #endregion - - #region Text Accessors - - /// - /// Gets the complete file content as a string. - /// - /// A string containing the complete file content. - public string GetText() - { - return this.scriptFile.Contents; - } - - /// - /// Gets the file content in the specified range as a string. - /// - /// The buffer range for which content will be extracted. - /// A string with the specified range of content. - public string GetText(BufferRange bufferRange) - { - return - string.Join( - Environment.NewLine, - this.GetTextLines(bufferRange)); - } - - /// - /// Gets the complete file content as an array of strings. - /// - /// An array of strings, each representing a line in the file. - public string[] GetTextLines() - { - return this.scriptFile.FileLines.ToArray(); - } - - /// - /// Gets the file content in the specified range as an array of strings. - /// - /// The buffer range for which content will be extracted. - /// An array of strings, each representing a line in the file within the specified range. - public string[] GetTextLines(BufferRange bufferRange) - { - return this.scriptFile.GetLinesInRange(bufferRange); - } - - #endregion - - #region Text Manipulation - - /// - /// Inserts a text string at the current cursor position represented by - /// the parent EditorContext's CursorPosition property. - /// - /// The text string to insert. - public void InsertText(string textToInsert) - { - // Is there a selection? - if (this.editorContext.SelectedRange.HasRange) - { - this.InsertText( - textToInsert, - this.editorContext.SelectedRange); - } - else - { - this.InsertText( - textToInsert, - this.editorContext.CursorPosition); - } - } - - /// - /// Inserts a text string at the specified buffer position. - /// - /// The text string to insert. - /// The position at which the text will be inserted. - public void InsertText(string textToInsert, BufferPosition insertPosition) - { - this.InsertText( - textToInsert, - new BufferRange(insertPosition, insertPosition)); - } - - /// - /// Inserts a text string at the specified line and column numbers. - /// - /// The text string to insert. - /// The 1-based line number at which the text will be inserted. - /// The 1-based column number at which the text will be inserted. - public void InsertText(string textToInsert, int insertLine, int insertColumn) - { - this.InsertText( - textToInsert, - new BufferPosition(insertLine, insertColumn)); - } - - /// - /// Inserts a text string to replace the specified range, represented - /// by starting and ending line and column numbers. Can be used to - /// insert, replace, or delete text depending on the specified range - /// and text to insert. - /// - /// The text string to insert. - /// The 1-based starting line number where text will be replaced. - /// The 1-based starting column number where text will be replaced. - /// The 1-based ending line number where text will be replaced. - /// The 1-based ending column number where text will be replaced. - public void InsertText( - string textToInsert, - int startLine, - int startColumn, - int endLine, - int endColumn) - { - this.InsertText( - textToInsert, - new BufferRange( - startLine, - startColumn, - endLine, - endColumn)); - } - - /// - /// Inserts a text string to replace the specified range. Can be - /// used to insert, replace, or delete text depending on the specified - /// range and text to insert. - /// - /// The text string to insert. - /// The buffer range which will be replaced by the string. - public void InsertText(string textToInsert, BufferRange insertRange) - { - this.editorOperations - .InsertTextAsync(this.scriptFile.ClientFilePath, textToInsert, insertRange) - .Wait(); - } - - #endregion - - #region File Manipulation - - /// - /// Saves this file. - /// - public void Save() - { - this.editorOperations.SaveFileAsync(this.scriptFile.FilePath); - } - - /// - /// Save this file under a new path and open a new editor window on that file. - /// - /// - /// the path where the file should be saved, - /// including the file name with extension as the leaf - /// - public void SaveAs(string newFilePath) - { - // Do some validation here so that we can provide a helpful error if the path won't work - string absolutePath = System.IO.Path.IsPathRooted(newFilePath) ? - newFilePath : - System.IO.Path.GetFullPath(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(this.scriptFile.FilePath), newFilePath)); - - if (File.Exists(absolutePath)) - { - throw new IOException(String.Format("The file '{0}' already exists", absolutePath)); - } - - this.editorOperations.SaveFileAsync(this.scriptFile.FilePath, newFilePath); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs deleted file mode 100644 index 7ef6bdf0e..000000000 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ /dev/null @@ -1,128 +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.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Extensions -{ - /// - /// Provides an interface that must be implemented by an editor - /// host to perform operations invoked by extensions written in - /// PowerShell. - /// - public interface IEditorOperations - { - /// - /// Gets the EditorContext for the editor's current state. - /// - /// A new EditorContext object. - Task GetEditorContextAsync(); - - /// - /// Gets the path to the editor's active workspace. - /// - /// The workspace path or null if there isn't one. - string GetWorkspacePath(); - - /// - /// Resolves the given file path relative to the current workspace path. - /// - /// The file path to be resolved. - /// The resolved file path. - string GetWorkspaceRelativePath(string filePath); - - /// - /// Causes a new untitled file to be created in the editor. - /// - /// A task that can be awaited for completion. - Task NewFileAsync(); - - /// - /// Causes a file to be opened in the editor. If the file is - /// already open, the editor must switch to the file. - /// - /// The path of the file to be opened. - /// A Task that can be tracked for completion. - Task OpenFileAsync(string filePath); - - /// - /// Causes a file to be opened in the editor. If the file is - /// already open, the editor must switch to the file. - /// You can specify whether the file opens as a preview or as a durable editor. - /// - /// The path of the file to be opened. - /// Determines wether the file is opened as a preview or as a durable editor. - /// A Task that can be tracked for completion. - Task OpenFileAsync(string filePath, bool preview); - - /// - /// Causes a file to be closed in the editor. - /// - /// The path of the file to be closed. - /// A Task that can be tracked for completion. - Task CloseFileAsync(string filePath); - - /// - /// Causes a file to be saved in the editor. - /// - /// The path of the file to be saved. - /// A Task that can be tracked for completion. - Task SaveFileAsync(string filePath); - - /// - /// Causes a file to be saved as a new file in a new editor window. - /// - /// the path of the current file being saved - /// the path of the new file where the current window content will be saved - /// - Task SaveFileAsync(string oldFilePath, string newFilePath); - - /// - /// Inserts text into the specified range for the file at the specified path. - /// - /// The path of the file which will have text inserted. - /// The text to insert into the file. - /// The range in the file to be replaced. - /// A Task that can be tracked for completion. - Task InsertTextAsync(string filePath, string insertText, BufferRange insertRange); - - /// - /// Causes the selection to be changed in the editor's active file buffer. - /// - /// The range over which the selection will be made. - /// A Task that can be tracked for completion. - Task SetSelectionAsync(BufferRange selectionRange); - - /// - /// Shows an informational message to the user. - /// - /// The message to be shown. - /// A Task that can be tracked for completion. - Task ShowInformationMessageAsync(string message); - - /// - /// Shows an error message to the user. - /// - /// The message to be shown. - /// A Task that can be tracked for completion. - Task ShowErrorMessageAsync(string message); - - /// - /// Shows a warning message to the user. - /// - /// The message to be shown. - /// A Task that can be tracked for completion. - Task ShowWarningMessageAsync(string message); - - /// - /// Sets the status bar message in the editor UI (if applicable). - /// - /// The message to be shown. - /// If non-null, a timeout in milliseconds for how long the message should remain visible. - /// A Task that can be tracked for completion. - Task SetStatusBarMessageAsync(string message, int? timeout); - } -} - diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Language/AstOperations.cs deleted file mode 100644 index 354e5f188..000000000 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ /dev/null @@ -1,343 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Diagnostics; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using System.Management.Automation.Language; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices -{ - using System.Management.Automation; - using System.Management.Automation.Language; - - /// - /// Provides common operations for the syntax tree of a parsed script. - /// - internal static class AstOperations - { - // TODO: When netstandard is upgraded to 2.0, see if - // Delegate.CreateDelegate can be used here instead - private static readonly MethodInfo s_extentCloneWithNewOffset = typeof(PSObject).GetTypeInfo().Assembly - .GetType("System.Management.Automation.Language.InternalScriptPosition") - .GetMethod("CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic); - - private static readonly SemaphoreSlim s_completionHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - /// - /// Gets completions for the symbol found in the Ast at - /// the given file offset. - /// - /// - /// The Ast which will be traversed to find a completable symbol. - /// - /// - /// The array of tokens corresponding to the scriptAst parameter. - /// - /// - /// The 1-based file offset at which a symbol will be located. - /// - /// - /// The PowerShellContext to use for gathering completions. - /// - /// An ILogger implementation used for writing log messages. - /// - /// A CancellationToken to cancel completion requests. - /// - /// - /// A CommandCompletion instance that contains completions for the - /// symbol at the given offset. - /// - static public async Task GetCompletionsAsync( - Ast scriptAst, - Token[] currentTokens, - int fileOffset, - PowerShellContext powerShellContext, - ILogger logger, - CancellationToken cancellationToken) - { - if (!s_completionHandle.Wait(0)) - { - return null; - } - - try - { - IScriptPosition cursorPosition = (IScriptPosition)s_extentCloneWithNewOffset.Invoke( - scriptAst.Extent.StartScriptPosition, - new object[] { fileOffset }); - - logger.Write( - LogLevel.Verbose, - string.Format( - "Getting completions at offset {0} (line: {1}, column: {2})", - fileOffset, - cursorPosition.LineNumber, - cursorPosition.ColumnNumber)); - - if (!powerShellContext.IsAvailable) - { - return null; - } - - var stopwatch = new Stopwatch(); - - // If the current runspace is out of process we can use - // CommandCompletion.CompleteInput because PSReadLine won't be taking up the - // main runspace. - if (powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken)) - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspaceHandle.Runspace; - stopwatch.Start(); - try - { - return CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: powerShell); - } - finally - { - stopwatch.Stop(); - logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); - } - } - } - - CommandCompletion commandCompletion = null; - await powerShellContext.InvokeOnPipelineThreadAsync( - pwsh => - { - stopwatch.Start(); - commandCompletion = CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: pwsh); - }); - stopwatch.Stop(); - logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); - - return commandCompletion; - } - finally - { - s_completionHandle.Release(); - } - } - - /// - /// Finds the symbol at a given file location - /// - /// The abstract syntax tree of the given script - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script - /// Includes full function definition ranges in the search. - /// SymbolReference of found symbol - static public SymbolReference FindSymbolAtPosition( - Ast scriptAst, - int lineNumber, - int columnNumber, - bool includeFunctionDefinitions = false) - { - FindSymbolVisitor symbolVisitor = - new FindSymbolVisitor( - lineNumber, - columnNumber, - includeFunctionDefinitions); - - scriptAst.Visit(symbolVisitor); - - return symbolVisitor.FoundSymbolReference; - } - - /// - /// Finds the symbol (always Command type) at a given file location - /// - /// The abstract syntax tree of the given script - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// SymbolReference of found command - static public SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumber, int columnNumber) - { - FindCommandVisitor commandVisitor = new FindCommandVisitor(lineNumber, columnNumber); - scriptAst.Visit(commandVisitor); - - return commandVisitor.FoundCommandReference; - } - - /// - /// Finds all references (including aliases) in a script for the given symbol - /// - /// The abstract syntax tree of the given script - /// The symbol that we are looking for referneces of - /// Dictionary maping cmdlets to aliases for finding alias references - /// Dictionary maping aliases to cmdlets for finding alias references - /// - static public IEnumerable FindReferencesOfSymbol( - Ast scriptAst, - SymbolReference symbolReference, - Dictionary> CmdletToAliasDictionary, - Dictionary AliasToCmdletDictionary) - { - // find the symbol evaluators for the node types we are handling - FindReferencesVisitor referencesVisitor = - new FindReferencesVisitor( - symbolReference, - CmdletToAliasDictionary, - AliasToCmdletDictionary); - scriptAst.Visit(referencesVisitor); - - return referencesVisitor.FoundReferences; - } - - /// - /// Finds all references (not including aliases) in a script for the given symbol - /// - /// The abstract syntax tree of the given script - /// The symbol that we are looking for referneces of - /// If this reference search needs aliases. - /// This should always be false and used for occurence requests - /// A collection of SymbolReference objects that are refrences to the symbolRefrence - /// not including aliases - static public IEnumerable FindReferencesOfSymbol( - ScriptBlockAst scriptAst, - SymbolReference foundSymbol, - bool needsAliases) - { - FindReferencesVisitor referencesVisitor = - new FindReferencesVisitor(foundSymbol); - scriptAst.Visit(referencesVisitor); - - return referencesVisitor.FoundReferences; - } - - /// - /// Finds the definition of the symbol - /// - /// The abstract syntax tree of the given script - /// The symbol that we are looking for the definition of - /// A SymbolReference of the definition of the symbolReference - static public SymbolReference FindDefinitionOfSymbol( - Ast scriptAst, - SymbolReference symbolReference) - { - FindDeclarationVisitor declarationVisitor = - new FindDeclarationVisitor( - symbolReference); - scriptAst.Visit(declarationVisitor); - - return declarationVisitor.FoundDeclaration; - } - - /// - /// Finds all symbols in a script - /// - /// The abstract syntax tree of the given script - /// The PowerShell version the Ast was generated from - /// A collection of SymbolReference objects - static public IEnumerable FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion) - { - IEnumerable symbolReferences = null; - - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - // if (powerShellVersion >= new Version(5,0)) - // { - //#if PowerShellv5 - // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); - // scriptAst.Visit(findSymbolsVisitor); - // symbolReferences = findSymbolsVisitor.SymbolReferences; - //#endif - // } - // else - - FindSymbolsVisitor findSymbolsVisitor = new FindSymbolsVisitor(); - scriptAst.Visit(findSymbolsVisitor); - symbolReferences = findSymbolsVisitor.SymbolReferences; - return symbolReferences; - } - - /// - /// Checks if a given ast represents the root node of a *.psd1 file. - /// - /// The abstract syntax tree of the given script - /// true if the AST represts a *.psd1 file, otherwise false - static public bool IsPowerShellDataFileAst(Ast ast) - { - // sometimes we don't have reliable access to the filename - // so we employ heuristics to check if the contents are - // part of a psd1 file. - return IsPowerShellDataFileAstNode( - new { Item = ast, Children = new List() }, - new Type[] { - typeof(ScriptBlockAst), - typeof(NamedBlockAst), - typeof(PipelineAst), - typeof(CommandExpressionAst), - typeof(HashtableAst) }, - 0); - } - - static private bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap, int level) - { - var levelAstTypeMatch = node.Item.GetType().Equals(levelAstMap[level]); - if (!levelAstTypeMatch) - { - return false; - } - - if (level == levelAstMap.Length - 1) - { - return levelAstTypeMatch; - } - - var astsFound = (node.Item as Ast).FindAll(a => a is Ast, false); - if (astsFound != null) - { - foreach (var astFound in astsFound) - { - if (!astFound.Equals(node.Item) - && node.Item.Equals(astFound.Parent) - && IsPowerShellDataFileAstNode( - new { Item = astFound, Children = new List() }, - levelAstMap, - level + 1)) - { - return true; - } - } - } - - return false; - } - - /// - /// Finds all files dot sourced in a script - /// - /// The abstract syntax tree of the given script - /// Pre-calculated value of $PSScriptRoot - /// - static public string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot) - { - FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor(psScriptRoot); - scriptAst.Visit(dotSourcedVisitor); - - return dotSourcedVisitor.DotSourcedFiles.ToArray(); - } - } -} diff --git a/src/PowerShellEditorServices/Language/CommandHelpers.cs b/src/PowerShellEditorServices/Language/CommandHelpers.cs deleted file mode 100644 index 9c72e0eef..000000000 --- a/src/PowerShellEditorServices/Language/CommandHelpers.cs +++ /dev/null @@ -1,114 +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 Microsoft.PowerShell.EditorServices.Utility; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides utility methods for working with PowerShell commands. - /// - public class CommandHelpers - { - private static HashSet NounBlackList = - new HashSet - { - "Module", - "Script", - "Package", - "PackageProvider", - "PackageSource", - "InstalledModule", - "InstalledScript", - "ScriptFileInfo", - "PSRepository" - }; - - /// - /// Gets the CommandInfo instance for a command with a particular name. - /// - /// The name of the command. - /// The PowerShellContext to use for running Get-Command. - /// A CommandInfo object with details about the specified command. - public static async Task GetCommandInfoAsync( - string commandName, - PowerShellContext powerShellContext) - { - Validate.IsNotNull(nameof(commandName), commandName); - - // Make sure the command's noun isn't blacklisted. This is - // currently necessary to make sure that Get-Command doesn't - // load PackageManagement or PowerShellGet because they cause - // a major slowdown in IntelliSense. - var commandParts = commandName.Split('-'); - if (commandParts.Length == 2 && NounBlackList.Contains(commandParts[1])) - { - return null; - } - - PSCommand command = new PSCommand(); - command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command"); - command.AddArgument(commandName); - command.AddParameter("ErrorAction", "Ignore"); - - return - (await powerShellContext - .ExecuteCommandAsync(command, false, false)) - .Select(o => o.BaseObject) - .OfType() - .FirstOrDefault(); - } - - /// - /// Gets the command's "Synopsis" documentation section. - /// - /// The CommandInfo instance for the command. - /// The PowerShellContext to use for getting command documentation. - /// - public static async Task GetCommandSynopsisAsync( - CommandInfo commandInfo, - PowerShellContext powerShellContext) - { - string synopsisString = string.Empty; - - PSObject helpObject = null; - - if (commandInfo != null && - (commandInfo.CommandType == CommandTypes.Cmdlet || - commandInfo.CommandType == CommandTypes.Function || - commandInfo.CommandType == CommandTypes.Filter)) - { - PSCommand command = new PSCommand(); - command.AddCommand(@"Microsoft.PowerShell.Core\Get-Help"); - command.AddArgument(commandInfo); - command.AddParameter("ErrorAction", "Ignore"); - - var results = await powerShellContext.ExecuteCommandAsync(command, false, false); - helpObject = results.FirstOrDefault(); - - if (helpObject != null) - { - // Extract the synopsis string from the object - synopsisString = - (string)helpObject.Properties["synopsis"].Value ?? - string.Empty; - - // Ignore the placeholder value for this field - if (string.Equals(synopsisString, "SHORT DESCRIPTION", System.StringComparison.CurrentCultureIgnoreCase)) - { - synopsisString = string.Empty; - } - } - } - - return synopsisString; - } - } -} - diff --git a/src/PowerShellEditorServices/Language/CompletionResults.cs b/src/PowerShellEditorServices/Language/CompletionResults.cs deleted file mode 100644 index fc8d2eb00..000000000 --- a/src/PowerShellEditorServices/Language/CompletionResults.cs +++ /dev/null @@ -1,340 +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.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Management.Automation; -using System.Text.RegularExpressions; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides the results of a single code completion request. - /// - public sealed class CompletionResults - { - #region Properties - - /// - /// Gets the completions that were found during the - /// completion request. - /// - public CompletionDetails[] Completions { get; private set; } - - /// - /// Gets the range in the buffer that should be replaced by this - /// completion result. - /// - public BufferRange ReplacedRange { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates an empty CompletionResults instance. - /// - public CompletionResults() - { - this.Completions = new CompletionDetails[0]; - this.ReplacedRange = new BufferRange(0, 0, 0, 0); - } - - internal static CompletionResults Create( - ScriptFile scriptFile, - CommandCompletion commandCompletion) - { - BufferRange replacedRange = null; - - // Only calculate the replacement range if there are completion results - if (commandCompletion.CompletionMatches.Count > 0) - { - replacedRange = - scriptFile.GetRangeBetweenOffsets( - commandCompletion.ReplacementIndex, - commandCompletion.ReplacementIndex + commandCompletion.ReplacementLength); - } - - return new CompletionResults - { - Completions = GetCompletionsArray(commandCompletion), - ReplacedRange = replacedRange - }; - } - - #endregion - - #region Private Methods - - private static CompletionDetails[] GetCompletionsArray( - CommandCompletion commandCompletion) - { - IEnumerable completionList = - commandCompletion.CompletionMatches.Select( - CompletionDetails.Create); - - return completionList.ToArray(); - } - - #endregion - } - - /// - /// Enumerates the completion types that may be returned. - /// - public enum CompletionType - { - /// - /// Completion type is unknown, either through being uninitialized or - /// having been created from an unsupported CompletionResult that was - /// returned by the PowerShell engine. - /// - Unknown = 0, - - /// - /// Identifies a completion for a command. - /// - Command, - - /// - /// Identifies a completion for a .NET method. - /// - Method, - - /// - /// Identifies a completion for a command parameter name. - /// - ParameterName, - - /// - /// Identifies a completion for a command parameter value. - /// - ParameterValue, - - /// - /// Identifies a completion for a .NET property. - /// - Property, - - /// - /// Identifies a completion for a variable name. - /// - Variable, - - /// - /// Identifies a completion for a namespace. - /// - Namespace, - - /// - /// Identifies a completion for a .NET type name. - /// - Type, - - /// - /// Identifies a completion for a PowerShell language keyword. - /// - Keyword, - - /// - /// Identifies a completion for a provider path (like a file system path) to a leaf item. - /// - File, - - /// - /// Identifies a completion for a provider path (like a file system path) to a container. - /// - Folder - } - - /// - /// Provides the details about a single completion result. - /// - [DebuggerDisplay("CompletionType = {CompletionType.ToString()}, CompletionText = {CompletionText}")] - public sealed class CompletionDetails - { - #region Properties - - /// - /// Gets the text that will be used to complete the statement - /// at the requested file offset. - /// - public string CompletionText { get; private set; } - - /// - /// Gets the text that should be dispayed in a drop-down completion list. - /// - public string ListItemText { get; private set; } - - /// - /// Gets the text that can be used to display a tooltip for - /// the statement at the requested file offset. - /// - public string ToolTipText { get; private set; } - - /// - /// Gets the name of the type which this symbol represents. - /// If the symbol doesn't have an inherent type, null will - /// be returned. - /// - public string SymbolTypeName { get; private set; } - - /// - /// Gets the CompletionType which identifies the type of this completion. - /// - public CompletionType CompletionType { get; private set; } - - #endregion - - #region Constructors - - internal static CompletionDetails Create(CompletionResult completionResult) - { - Validate.IsNotNull("completionResult", completionResult); - - // Some tooltips may have newlines or whitespace for unknown reasons - string toolTipText = completionResult.ToolTip; - if (toolTipText != null) - { - toolTipText = toolTipText.Trim(); - } - - return new CompletionDetails - { - CompletionText = completionResult.CompletionText, - ListItemText = completionResult.ListItemText, - ToolTipText = toolTipText, - SymbolTypeName = ExtractSymbolTypeNameFromToolTip(completionResult.ToolTip), - CompletionType = - ConvertCompletionResultType( - completionResult.ResultType) - }; - } - - internal static CompletionDetails Create( - string completionText, - CompletionType completionType, - string toolTipText = null, - string symbolTypeName = null, - string listItemText = null) - { - return new CompletionDetails - { - CompletionText = completionText, - CompletionType = completionType, - ListItemText = listItemText, - ToolTipText = toolTipText, - SymbolTypeName = symbolTypeName - }; - } - - #endregion - - #region Public Methods - - /// - /// Compares two CompletionResults instances for equality. - /// - /// The potential CompletionResults instance to compare. - /// True if the CompletionResults instances have the same details. - public override bool Equals(object obj) - { - CompletionDetails otherDetails = obj as CompletionDetails; - if (otherDetails == null) - { - return false; - } - - return - string.Equals(this.CompletionText, otherDetails.CompletionText) && - this.CompletionType == otherDetails.CompletionType && - string.Equals(this.ToolTipText, otherDetails.ToolTipText) && - string.Equals(this.SymbolTypeName, otherDetails.SymbolTypeName); - } - - /// - /// Returns the hash code for this CompletionResults instance. - /// - /// The hash code for this CompletionResults instance. - public override int GetHashCode() - { - return - string.Format( - "{0}{1}{2}{3}{4}", - this.CompletionText, - this.CompletionType, - this.ListItemText, - this.ToolTipText, - this.SymbolTypeName).GetHashCode(); - } - - #endregion - - #region Private Methods - - private static CompletionType ConvertCompletionResultType( - CompletionResultType completionResultType) - { - switch (completionResultType) - { - case CompletionResultType.Command: - return CompletionType.Command; - - case CompletionResultType.Method: - return CompletionType.Method; - - case CompletionResultType.ParameterName: - return CompletionType.ParameterName; - - case CompletionResultType.ParameterValue: - return CompletionType.ParameterValue; - - case CompletionResultType.Property: - return CompletionType.Property; - - case CompletionResultType.Variable: - return CompletionType.Variable; - - case CompletionResultType.Namespace: - return CompletionType.Namespace; - - case CompletionResultType.Type: - return CompletionType.Type; - - case CompletionResultType.Keyword: - return CompletionType.Keyword; - - case CompletionResultType.ProviderContainer: - return CompletionType.Folder; - - case CompletionResultType.ProviderItem: - return CompletionType.File; - - default: - // TODO: Trace the unsupported CompletionResultType - return CompletionType.Unknown; - } - } - - private static string ExtractSymbolTypeNameFromToolTip(string toolTipText) - { - // Tooltips returned from PowerShell contain the symbol type in - // brackets. Attempt to extract such strings for further processing. - var matches = Regex.Matches(toolTipText, @"^\[(.+)\]"); - - if (matches.Count > 0 && matches[0].Groups.Count > 1) - { - // Return the symbol type name - return matches[0].Groups[1].Value; - } - - return null; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs b/src/PowerShellEditorServices/Language/FindCommandVisitor.cs deleted file mode 100644 index 5e5fca624..000000000 --- a/src/PowerShellEditorServices/Language/FindCommandVisitor.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. -// - -using System.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// The vistior used to find the commandAst of a specific location in an AST - /// - internal class FindCommandVisitor : AstVisitor - { - private int lineNumber; - private int columnNumber; - - public SymbolReference FoundCommandReference { get; private set; } - - public FindCommandVisitor(int lineNumber, int columnNumber) - { - this.lineNumber = lineNumber; - this.columnNumber = columnNumber; - } - - public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) - { - if (this.lineNumber == pipelineAst.Extent.StartLineNumber) - { - // Which command is the cursor in? - foreach (var commandAst in pipelineAst.PipelineElements.OfType()) - { - int trueEndColumnNumber = commandAst.Extent.EndColumnNumber; - string currentLine = commandAst.Extent.StartScriptPosition.Line; - - if (currentLine.Length >= trueEndColumnNumber) - { - // Get the text left in the line after the command's extent - string remainingLine = - currentLine.Substring( - commandAst.Extent.EndColumnNumber); - - // Calculate the "true" end column number by finding out how many - // whitespace characters are between this command and the next (or - // the end of the line). - // NOTE: +1 is added to trueEndColumnNumber to account for the position - // just after the last character in the command string or script line. - int preTrimLength = remainingLine.Length; - int postTrimLength = remainingLine.TrimStart().Length; - trueEndColumnNumber = - commandAst.Extent.EndColumnNumber + - (preTrimLength - postTrimLength) + 1; - } - - if (commandAst.Extent.StartColumnNumber <= columnNumber && - trueEndColumnNumber >= columnNumber) - { - this.FoundCommandReference = - new SymbolReference( - SymbolType.Function, - commandAst.CommandElements[0].Extent); - - return AstVisitAction.StopVisit; - } - } - } - - return base.VisitPipeline(pipelineAst); - } - - /// - /// Is the position of the given location is in the range of the start - /// of the first element to the character before the second element - /// - /// The script extent of the first element of the command ast - /// The script extent of the second element of the command ast - /// True if the given position is in the range of the start of - /// the first element to the character before the second element - private bool IsPositionInExtent(IScriptExtent firstExtent, IScriptExtent secondExtent) - { - return (firstExtent.StartLineNumber == lineNumber && - firstExtent.StartColumnNumber <= columnNumber && - secondExtent.StartColumnNumber >= columnNumber - 1); - } - } -} diff --git a/src/PowerShellEditorServices/Language/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Language/FindDeclarationVisitor.cs deleted file mode 100644 index f8ecffc2e..000000000 --- a/src/PowerShellEditorServices/Language/FindDeclarationVisitor.cs +++ /dev/null @@ -1,148 +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.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// The visitor used to find the definition of a symbol - /// - internal class FindDeclarationVisitor : AstVisitor - { - private SymbolReference symbolRef; - private string variableName; - - public SymbolReference FoundDeclaration{ get; private set; } - - public FindDeclarationVisitor(SymbolReference symbolRef) - { - this.symbolRef = symbolRef; - if (this.symbolRef.SymbolType == SymbolType.Variable) - { - // converts `$varName` to `varName` or of the form ${varName} to varName - variableName = symbolRef.SymbolName.TrimStart('$').Trim('{', '}'); - } - } - - /// - /// Decides if the current function definition is the right definition - /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Function and have the same name as the symbol - /// - /// A FunctionDefinitionAst in the script's AST - /// A decision to stop searching if the right FunctionDefinitionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - // Get the start column number of the function name, - // instead of the the start column of 'function' and create new extent for the functionName - int startColumnNumber = - functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name) + 1; - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length - }; - - if (symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(symbolRef.ScriptRegion.Text, StringComparison.CurrentCultureIgnoreCase)) - { - this.FoundDeclaration = - new SymbolReference( - SymbolType.Function, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return base.VisitFunctionDefinition(functionDefinitionAst); - } - - /// - /// Check if the left hand side of an assignmentStatementAst is a VariableExpressionAst - /// with the same name as that of symbolRef. - /// - /// An AssignmentStatementAst - /// A decision to stop searching if the right VariableExpressionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) - { - if (variableName == null) - { - return AstVisitAction.Continue; - } - - // We want to check VariableExpressionAsts from within this AssignmentStatementAst so we visit it. - FindDeclarationVariableExpressionVisitor visitor = new FindDeclarationVariableExpressionVisitor(symbolRef); - assignmentStatementAst.Left.Visit(visitor); - - if (visitor.FoundDeclaration != null) - { - FoundDeclaration = visitor.FoundDeclaration; - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - /// - /// The private visitor used to find the variable expression that matches a symbol - /// - private class FindDeclarationVariableExpressionVisitor : AstVisitor - { - private SymbolReference symbolRef; - private string variableName; - - public SymbolReference FoundDeclaration{ get; private set; } - - public FindDeclarationVariableExpressionVisitor(SymbolReference symbolRef) - { - this.symbolRef = symbolRef; - if (this.symbolRef.SymbolType == SymbolType.Variable) - { - // converts `$varName` to `varName` or of the form ${varName} to varName - variableName = symbolRef.SymbolName.TrimStart('$').Trim('{', '}'); - } - } - - /// - /// Check if the VariableExpressionAst has the same name as that of symbolRef. - /// - /// A VariableExpressionAst - /// A decision to stop searching if the right VariableExpressionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (variableExpressionAst.VariablePath.UserPath.Equals(variableName, StringComparison.OrdinalIgnoreCase)) - { - // TODO also find instances of set-variable - FoundDeclaration = new SymbolReference(SymbolType.Variable, variableExpressionAst.Extent); - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitMemberExpression(MemberExpressionAst functionDefinitionAst) - { - // We don't want to discover any variables in member expressisons (`$something.Foo`) - return AstVisitAction.SkipChildren; - } - - public override AstVisitAction VisitIndexExpression(IndexExpressionAst functionDefinitionAst) - { - // We don't want to discover any variables in index expressions (`$something[0]`) - return AstVisitAction.SkipChildren; - } - } - } -} diff --git a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs deleted file mode 100644 index c0295a228..000000000 --- a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs +++ /dev/null @@ -1,90 +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.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// The vistor used to find the dont sourced files in an AST - /// - internal class FindDotSourcedVisitor : AstVisitor - { - private readonly string _psScriptRoot; - - /// - /// A hash set of the dot sourced files (because we don't want duplicates) - /// - public HashSet DotSourcedFiles { get; private set; } - - /// - /// Creates a new instance of the FindDotSourcedVisitor class. - /// - /// Pre-calculated value of $PSScriptRoot - public FindDotSourcedVisitor(string psScriptRoot) - { - DotSourcedFiles = new HashSet(StringComparer.CurrentCultureIgnoreCase); - _psScriptRoot = psScriptRoot; - } - - /// - /// Checks to see if the command invocation is a dot - /// in order to find a dot sourced file - /// - /// A CommandAst object in the script's AST - /// A decision to stop searching if the right commandAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - CommandElementAst commandElementAst = commandAst.CommandElements[0]; - if (commandAst.InvocationOperator.Equals(TokenKind.Dot)) - { - string path; - switch (commandElementAst) - { - case StringConstantExpressionAst stringConstantExpressionAst: - path = stringConstantExpressionAst.Value; - break; - - case ExpandableStringExpressionAst expandableStringExpressionAst: - path = GetPathFromExpandableStringExpression(expandableStringExpressionAst); - break; - - default: - path = null; - break; - } - - if (!string.IsNullOrWhiteSpace(path)) - { - DotSourcedFiles.Add(PathUtils.NormalizePathSeparators(path)); - } - } - - return base.VisitCommand(commandAst); - } - - private string GetPathFromExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) - { - var path = expandableStringExpressionAst.Value; - foreach (var nestedExpression in expandableStringExpressionAst.NestedExpressions) - { - // If the string contains the variable $PSScriptRoot, we replace it with the corresponding value. - if (!(nestedExpression is VariableExpressionAst variableAst - && variableAst.VariablePath.UserPath.Equals("PSScriptRoot", StringComparison.OrdinalIgnoreCase))) - { - return null; // We return null instead of a partially evaluated ExpandableStringExpression. - } - - path = path.Replace(variableAst.ToString(), _psScriptRoot); - } - - return path; - } - } -} diff --git a/src/PowerShellEditorServices/Language/FindOccurrencesResult.cs b/src/PowerShellEditorServices/Language/FindOccurrencesResult.cs deleted file mode 100644 index 92f795ca4..000000000 --- a/src/PowerShellEditorServices/Language/FindOccurrencesResult.cs +++ /dev/null @@ -1,23 +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.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// A class for the found occurences of a symbol. - /// It contains a collection of symbol references. - /// - public class FindOccurrencesResult - { - #region Properties - /// - /// Gets the collection of SymboleReferences for the all occurences of the symbol - /// - public IEnumerable FoundOccurrences { get; internal set; } - #endregion - } -} diff --git a/src/PowerShellEditorServices/Language/FindReferencesResult.cs b/src/PowerShellEditorServices/Language/FindReferencesResult.cs deleted file mode 100644 index 16396a75a..000000000 --- a/src/PowerShellEditorServices/Language/FindReferencesResult.cs +++ /dev/null @@ -1,33 +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.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// A class to contain the found references of a symbol. - /// It contains a collection of symbol references, the symbol name, and the symbol's file offset - /// - public class FindReferencesResult - { - #region Properties - /// - /// Gets the name of the symbol - /// - public string SymbolName { get; internal set; } - - /// - /// Gets the file offset (location based on line and column number) of the symbol - /// - public int SymbolFileOffset { get; internal set; } - - /// - /// Gets the collection of SymboleReferences for the all references to the symbol - /// - public IEnumerable FoundReferences { get; internal set; } - #endregion - } -} diff --git a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs deleted file mode 100644 index 2b7b160ea..000000000 --- a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs +++ /dev/null @@ -1,189 +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.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// The visitor used to find the references of a symbol in a script's AST - /// - internal class FindReferencesVisitor : AstVisitor - { - private SymbolReference symbolRef; - private Dictionary> CmdletToAliasDictionary; - private Dictionary AliasToCmdletDictionary; - private string symbolRefCommandName; - private bool needsAliases; - - public List FoundReferences { get; set; } - - /// - /// Constructor used when searching for aliases is needed - /// - /// The found symbolReference that other symbols are being compared to - /// Dictionary maping cmdlets to aliases for finding alias references - /// Dictionary maping aliases to cmdlets for finding alias references - public FindReferencesVisitor( - SymbolReference symbolReference, - Dictionary> CmdletToAliasDictionary, - Dictionary AliasToCmdletDictionary) - { - this.symbolRef = symbolReference; - this.FoundReferences = new List(); - this.needsAliases = true; - this.CmdletToAliasDictionary = CmdletToAliasDictionary; - this.AliasToCmdletDictionary = AliasToCmdletDictionary; - - // Try to get the symbolReference's command name of an alias, - // if a command name does not exists (if the symbol isn't an alias to a command) - // set symbolRefCommandName to and empty string value - AliasToCmdletDictionary.TryGetValue(symbolReference.ScriptRegion.Text, out symbolRefCommandName); - if (symbolRefCommandName == null) { symbolRefCommandName = string.Empty; } - - } - - /// - /// Constructor used when searching for aliases is not needed - /// - /// The found symbolReference that other symbols are being compared to - public FindReferencesVisitor(SymbolReference foundSymbol) - { - this.symbolRef = foundSymbol; - this.FoundReferences = new List(); - this.needsAliases = false; - } - - /// - /// Decides if the current command is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Function - /// and have the same name as the symbol - /// - /// A CommandAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - Ast commandNameAst = commandAst.CommandElements[0]; - string commandName = commandNameAst.Extent.Text; - - if(symbolRef.SymbolType.Equals(SymbolType.Function)) - { - if (needsAliases) - { - // Try to get the commandAst's name and aliases, - // if a command does not exists (if the symbol isn't an alias to a command) - // set command to and empty string value string command - // if the aliases do not exist (if the symvol isn't a command that has aliases) - // set aliases to an empty List - string command; - List alaises; - CmdletToAliasDictionary.TryGetValue(commandName, out alaises); - AliasToCmdletDictionary.TryGetValue(commandName, out command); - if (alaises == null) { alaises = new List(); } - if (command == null) { command = string.Empty; } - - if (symbolRef.SymbolType.Equals(SymbolType.Function)) - { - // Check if the found symbol's name is the same as the commandAst's name OR - // if the symbol's name is an alias for this commandAst's name (commandAst is a cmdlet) OR - // if the symbol's name is the same as the commandAst's cmdlet name (commandAst is a alias) - if (commandName.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase) || - alaises.Contains(symbolRef.ScriptRegion.Text.ToLower()) || - command.Equals(symbolRef.ScriptRegion.Text, StringComparison.CurrentCultureIgnoreCase) || - (!command.Equals(string.Empty) && command.Equals(symbolRefCommandName, StringComparison.CurrentCultureIgnoreCase))) - { - this.FoundReferences.Add(new SymbolReference( - SymbolType.Function, - commandNameAst.Extent)); - } - } - - } - else // search does not include aliases - { - if (commandName.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - this.FoundReferences.Add(new SymbolReference( - SymbolType.Function, - commandNameAst.Extent)); - } - } - - } - return base.VisitCommand(commandAst); - } - - /// - /// Decides if the current function definition is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Function and have the same name as the symbol - /// - /// A functionDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - // Get the start column number of the function name, - // instead of the the start column of 'function' and create new extent for the functionName - int startColumnNumber = - functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name) + 1; - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length - }; - - if (symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - this.FoundReferences.Add(new SymbolReference( - SymbolType.Function, - nameExtent)); - } - return base.VisitFunctionDefinition(functionDefinitionAst); - } - - /// - /// Decides if the current function definition is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Parameter and have the same name as the symbol - /// - /// A commandParameterAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) - { - if (symbolRef.SymbolType.Equals(SymbolType.Parameter) && - commandParameterAst.Extent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - this.FoundReferences.Add(new SymbolReference( - SymbolType.Parameter, - commandParameterAst.Extent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current function definition is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Variable and have the same name as the symbol - /// - /// A variableExpressionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if(symbolRef.SymbolType.Equals(SymbolType.Variable) && - variableExpressionAst.Extent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - this.FoundReferences.Add(new SymbolReference( - SymbolType.Variable, - variableExpressionAst.Extent)); - } - return AstVisitAction.Continue; - } - } -} diff --git a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs deleted file mode 100644 index 404f8d190..000000000 --- a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs +++ /dev/null @@ -1,145 +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.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// The visitor used to find the the symbol at a specfic location in the AST - /// - internal class FindSymbolVisitor : AstVisitor - { - private int lineNumber; - private int columnNumber; - private bool includeFunctionDefinitions; - - public SymbolReference FoundSymbolReference { get; private set; } - - public FindSymbolVisitor( - int lineNumber, - int columnNumber, - bool includeFunctionDefinitions) - { - this.lineNumber = lineNumber; - this.columnNumber = columnNumber; - this.includeFunctionDefinitions = includeFunctionDefinitions; - } - - /// - /// Checks to see if this command ast is the symbol we are looking for. - /// - /// A CommandAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - Ast commandNameAst = commandAst.CommandElements[0]; - - if (this.IsPositionInExtent(commandNameAst.Extent)) - { - this.FoundSymbolReference = - new SymbolReference( - SymbolType.Function, - commandNameAst.Extent); - - return AstVisitAction.StopVisit; - } - - return base.VisitCommand(commandAst); - } - - /// - /// Checks to see if this function definition is the symbol we are looking for. - /// - /// A functionDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - int startColumnNumber = 1; - - if (!this.includeFunctionDefinitions) - { - startColumnNumber = - functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name) + 1; - } - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length - }; - - if (this.IsPositionInExtent(nameExtent)) - { - this.FoundSymbolReference = - new SymbolReference( - SymbolType.Function, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return base.VisitFunctionDefinition(functionDefinitionAst); - } - - /// - /// Checks to see if this command parameter is the symbol we are looking for. - /// - /// A CommandParameterAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) - { - if (this.IsPositionInExtent(commandParameterAst.Extent)) - { - this.FoundSymbolReference = - new SymbolReference( - SymbolType.Parameter, - commandParameterAst.Extent); - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this variable expression is the symbol we are looking for. - /// - /// A VariableExpressionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (this.IsPositionInExtent(variableExpressionAst.Extent)) - { - this.FoundSymbolReference = - new SymbolReference( - SymbolType.Variable, - variableExpressionAst.Extent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Is the position of the given location is in the ast's extent - /// - /// The script extent of the element - /// True if the given position is in the range of the element's extent - private bool IsPositionInExtent(IScriptExtent extent) - { - return (extent.StartLineNumber == lineNumber && - extent.StartColumnNumber <= columnNumber && - extent.EndColumnNumber >= columnNumber); - } - } -} diff --git a/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs deleted file mode 100644 index 96f5c335c..000000000 --- a/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs +++ /dev/null @@ -1,148 +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.Collections.Generic; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// The visitor used to find all the symbols (function and class defs) in the AST. - /// - /// - /// Requires PowerShell v3 or higher - /// - internal class FindSymbolsVisitor : AstVisitor - { - public List SymbolReferences { get; private set; } - - public FindSymbolsVisitor() - { - this.SymbolReferences = new List(); - } - - /// - /// Adds each function definition as a - /// - /// A functionDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - IScriptExtent nameExtent = new ScriptExtent() { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber - }; - - SymbolType symbolType = - functionDefinitionAst.IsWorkflow ? - SymbolType.Workflow : SymbolType.Function; - - this.SymbolReferences.Add( - new SymbolReference( - symbolType, - nameExtent)); - - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this variable expression is the symbol we are looking for. - /// - /// A VariableExpressionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (!IsAssignedAtScriptScope(variableExpressionAst)) - { - return AstVisitAction.Continue; - } - - this.SymbolReferences.Add( - new SymbolReference( - SymbolType.Variable, - variableExpressionAst.Extent)); - - return AstVisitAction.Continue; - } - - private bool IsAssignedAtScriptScope(VariableExpressionAst variableExpressionAst) - { - Ast parent = variableExpressionAst.Parent; - if (!(parent is AssignmentStatementAst)) - { - return false; - } - - parent = parent.Parent; - if (parent == null || parent.Parent == null || parent.Parent.Parent == null) - { - return true; - } - - return false; - } - } - - /// - /// Visitor to find all the keys in Hashtable AST - /// - internal class FindHashtableSymbolsVisitor : AstVisitor - { - /// - /// List of symbols (keys) found in the hashtable - /// - public List SymbolReferences { get; private set; } - - /// - /// Initializes a new instance of FindHashtableSymbolsVisitor class - /// - public FindHashtableSymbolsVisitor() - { - SymbolReferences = new List(); - } - - /// - /// Adds keys in the input hashtable to the symbol reference - /// - public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) - { - if (hashtableAst.KeyValuePairs == null) - { - return AstVisitAction.Continue; - } - - foreach (var kvp in hashtableAst.KeyValuePairs) - { - var keyStrConstExprAst = kvp.Item1 as StringConstantExpressionAst; - if (keyStrConstExprAst != null) - { - IScriptExtent nameExtent = new ScriptExtent() - { - Text = keyStrConstExprAst.Value, - StartLineNumber = kvp.Item1.Extent.StartLineNumber, - EndLineNumber = kvp.Item2.Extent.EndLineNumber, - StartColumnNumber = kvp.Item1.Extent.StartColumnNumber, - EndColumnNumber = kvp.Item2.Extent.EndColumnNumber - }; - - SymbolType symbolType = SymbolType.HashtableKey; - - this.SymbolReferences.Add( - new SymbolReference( - symbolType, - nameExtent)); - - } - } - - return AstVisitAction.Continue; - } - } -} diff --git a/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs deleted file mode 100644 index 03628ee3e..000000000 --- a/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs +++ /dev/null @@ -1,80 +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.Collections.Generic; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - - ///// - ///// The visitor used to find all the symbols (function and class defs) in the AST. - ///// - ///// - ///// Requires PowerShell v5 or higher - ///// - ///// - //internal class FindSymbolsVisitor2 : AstVisitor2 - //{ - // private FindSymbolsVisitor findSymbolsVisitor; - - // public List SymbolReferences - // { - // get - // { - // return this.findSymbolsVisitor.SymbolReferences; - // } - // } - - // public FindSymbolsVisitor2() - // { - // this.findSymbolsVisitor = new FindSymbolsVisitor(); - // } - - // /// - // /// Adds each function definition as a - // /// - // /// A functionDefinitionAst object in the script's AST - // /// A decision to stop searching if the right symbol was found, - // /// or a decision to continue if it wasn't found - // public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - // { - // return this.findSymbolsVisitor.VisitFunctionDefinition(functionDefinitionAst); - // } - - // /// - // /// Checks to see if this variable expression is the symbol we are looking for. - // /// - // /// A VariableExpressionAst object in the script's AST - // /// A decision to stop searching if the right symbol was found, - // /// or a decision to continue if it wasn't found - // public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - // { - // return this.findSymbolsVisitor.VisitVariableExpression(variableExpressionAst); - // } - - // public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - // { - // IScriptExtent nameExtent = new ScriptExtent() - // { - // Text = configurationDefinitionAst.InstanceName.Extent.Text, - // StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - // EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber, - // StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber, - // EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber - // }; - - // this.findSymbolsVisitor.SymbolReferences.Add( - // new SymbolReference( - // SymbolType.Configuration, - // nameExtent)); - - // return AstVisitAction.Continue; - // } - //} -} - diff --git a/src/PowerShellEditorServices/Language/FoldingReference.cs b/src/PowerShellEditorServices/Language/FoldingReference.cs deleted file mode 100644 index 2b8a14502..000000000 --- a/src/PowerShellEditorServices/Language/FoldingReference.cs +++ /dev/null @@ -1,112 +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; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// A class that holds the information for a foldable region of text in a document - /// - public class FoldingReference: IComparable - { - /// - /// The zero-based line number from where the folded range starts. - /// - public int StartLine { get; set; } - - /// - /// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. - /// - public int StartCharacter { get; set; } = 0; - - /// - /// The zero-based line number where the folded range ends. - /// - public int EndLine { get; set; } - - /// - /// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. - /// - public int EndCharacter { get; set; } = 0; - - /// - /// Describes the kind of the folding range such as `comment' or 'region'. - /// - public string Kind { get; set; } - - /// - /// A custom comparable method which can properly sort FoldingReference objects - /// - public int CompareTo(FoldingReference that) { - // Initially look at the start line - if (this.StartLine < that.StartLine) { return -1; } - if (this.StartLine > that.StartLine) { return 1; } - - // They have the same start line so now consider the end line. - // The biggest line range is sorted first - if (this.EndLine > that.EndLine) { return -1; } - if (this.EndLine < that.EndLine) { return 1; } - - // They have the same lines, but what about character offsets - if (this.StartCharacter < that.StartCharacter) { return -1; } - if (this.StartCharacter > that.StartCharacter) { return 1; } - if (this.EndCharacter < that.EndCharacter) { return -1; } - if (this.EndCharacter > that.EndCharacter) { return 1; } - - // They're the same range, but what about kind - return string.Compare(this.Kind, that.Kind); - } - } - - /// - /// A class that holds a list of FoldingReferences and ensures that when adding a reference that the - /// folding rules are obeyed, e.g. Only one fold per start line - /// - public class FoldingReferenceList - { - private readonly Dictionary references = new Dictionary(); - - /// - /// Return all references in the list - /// - public IEnumerable References - { - get - { - return references.Values; - } - } - - /// - /// Adds a FoldingReference to the list and enforces ordering rules e.g. Only one fold per start line - /// - public void SafeAdd(FoldingReference item) - { - if (item == null) { return; } - - // Only add the item if it hasn't been seen before or it's the largest range - if (references.TryGetValue(item.StartLine, out FoldingReference currentItem)) - { - if (currentItem.CompareTo(item) == 1) { references[item.StartLine] = item; } - } - else - { - references[item.StartLine] = item; - } - } - - /// - /// Helper method to easily convert the Dictionary Values into an array - /// - public FoldingReference[] ToArray() - { - var result = new FoldingReference[references.Count]; - references.Values.CopyTo(result, 0); - return result; - } - } -} diff --git a/src/PowerShellEditorServices/Language/FullScriptExtent.cs b/src/PowerShellEditorServices/Language/FullScriptExtent.cs deleted file mode 100644 index 3db5b9f5d..000000000 --- a/src/PowerShellEditorServices/Language/FullScriptExtent.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides an IScriptExtent implementation that is aware of editor context - /// and can adjust to changes. - /// - public class FullScriptExtent : IScriptExtent - { - #region Properties - - /// - /// Gets the buffer range of the extent. - /// - public BufferRange BufferRange { get; private set; } - - /// - /// Gets the FileContext that this extent refers to. - /// - public FileContext FileContext { get; } - - /// - /// Gets the file path of the script file in which this extent is contained. - /// - public string File - { - get { return FileContext.Path; } - } - - /// - /// Gets the starting script position of the extent. - /// - public IScriptPosition StartScriptPosition - { - get { return new FullScriptPosition(FileContext, BufferRange.Start, StartOffset); } - } - - /// - /// Gets the ending script position of the extent. - /// - public IScriptPosition EndScriptPosition - { - get { return new FullScriptPosition(FileContext, BufferRange.End, EndOffset); } - } - - /// - /// Gets the starting line number of the extent. - /// - public int StartLineNumber - { - get { return BufferRange.Start.Line; } - } - - - /// - /// Gets the starting column number of the extent. - /// - public int StartColumnNumber - { - get { return BufferRange.Start.Column; } - } - - /// - /// Gets the ending line number of the extent. - /// - public int EndLineNumber - { - get { return BufferRange.End.Line; } - } - - /// - /// Gets the ending column number of the extent. - /// - public int EndColumnNumber - { - get { return BufferRange.End.Column; } - } - - /// - /// Gets the text that is contained within the extent. - /// - public string Text - { - get - { - // StartOffset can be > the length for the EOF token. - if (StartOffset > FileContext.scriptFile.Contents.Length) - { - return ""; - } - - return FileContext.GetText(BufferRange); - } - } - - /// - /// Gets the starting file offset of the extent. - /// - public int StartOffset { get; private set; } - - /// - /// Gets the ending file offset of the extent. - /// - public int EndOffset { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the FullScriptExtent class. - /// - /// The FileContext this extent refers to. - /// The buffer range this extent is located at. - public FullScriptExtent(FileContext fileContext, BufferRange bufferRange) - { - Validate.IsNotNull(nameof(fileContext), fileContext); - Validate.IsNotNull(nameof(bufferRange), bufferRange); - - BufferRange = bufferRange; - FileContext = fileContext; - - StartOffset = fileContext.scriptFile.GetOffsetAtPosition( - bufferRange.Start.Line, - bufferRange.Start.Column); - - EndOffset = fileContext.scriptFile.GetOffsetAtPosition( - bufferRange.End.Line, - bufferRange.End.Column); - } - - /// - /// Creates an new instance of the FullScriptExtent class. - /// - /// The FileContext this extent refers to. - /// The zero based offset this extent starts at. - /// The zero based offset this extent ends at. - public FullScriptExtent(FileContext fileContext, int startOffset, int endOffset) - { - Validate.IsNotNull(nameof(fileContext), fileContext); - Validate.IsNotNull(nameof(startOffset), startOffset); - Validate.IsNotNull(nameof(endOffset), endOffset); - - FileContext = fileContext; - StartOffset = startOffset; - EndOffset = endOffset; - BufferRange = fileContext.scriptFile.GetRangeBetweenOffsets(startOffset, endOffset); - } - - #endregion - - #region Public Methods - - /// - /// Return the text this extent refers to. - /// - public override string ToString() - { - return Text; - } - - /// - /// Moves the start and end positions of the extent by an offset. Can - /// be used to move forwards or backwards. - /// - /// The amount to move the extent. - public void AddOffset(int offset) { - StartOffset += offset; - EndOffset += offset; - - BufferRange = FileContext.scriptFile.GetRangeBetweenOffsets(StartOffset, EndOffset); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Language/FullScriptPosition.cs b/src/PowerShellEditorServices/Language/FullScriptPosition.cs deleted file mode 100644 index c9a24b6fd..000000000 --- a/src/PowerShellEditorServices/Language/FullScriptPosition.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Extensions; - -namespace Microsoft.PowerShell.EditorServices -{ - internal class FullScriptPosition : IScriptPosition - { - #region Fields - private readonly FileContext fileContext; - - private readonly BufferPosition bufferPosition; - - #endregion - - #region Properties - public string File - { - get { return fileContext.Path; } - } - public int LineNumber - { - get { return bufferPosition.Line; } - } - public int ColumnNumber - { - get { return bufferPosition.Column; } - } - public string Line - { - get { return fileContext.scriptFile.GetLine(LineNumber); } - } - public int Offset { get; } - - #endregion - - #region Constructors - - internal FullScriptPosition(FileContext context, BufferPosition position, int offset) - { - fileContext = context; - bufferPosition = position; - Offset = offset; - } - - #endregion - - - #region Public Methods - - public string GetFullScript() - { - return fileContext.GetText(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Language/GetDefinitionResult.cs b/src/PowerShellEditorServices/Language/GetDefinitionResult.cs deleted file mode 100644 index 753e027a1..000000000 --- a/src/PowerShellEditorServices/Language/GetDefinitionResult.cs +++ /dev/null @@ -1,30 +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 -{ - /// - /// A class to contain the found definition of a symbol. - /// It contains the symbol reference of the definition - /// - public class GetDefinitionResult - { - #region Properties - /// - /// Gets the symbolReference of the found definition - /// - public SymbolReference FoundDefinition { get; internal set; } - #endregion - - /// - /// Constructs an instance of a GetDefinitionResut - /// - /// The symbolRefernece for the found definition - public GetDefinitionResult(SymbolReference symRef) - { - FoundDefinition = symRef; - } - } -} diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs deleted file mode 100644 index 486306b58..000000000 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ /dev/null @@ -1,887 +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 Microsoft.PowerShell.EditorServices.Symbols; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Security; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides a high-level service for performing code completion and - /// navigation operations on PowerShell scripts. - /// - public class LanguageService - { - #region Private Fields - - const int DefaultWaitTimeoutMilliseconds = 5000; - - private readonly ILogger _logger; - - private readonly PowerShellContext _powerShellContext; - - private readonly Dictionary> _cmdletToAliasDictionary; - - private readonly Dictionary _aliasToCmdletDictionary; - - private readonly IDocumentSymbolProvider[] _documentSymbolProviders; - - private readonly SemaphoreSlim _aliasHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - private bool _areAliasesLoaded; - - private CompletionResults _mostRecentCompletions; - - private int _mostRecentRequestLine; - - private int _mostRecentRequestOffest; - - private string _mostRecentRequestFile; - - #endregion - - #region Constructors - - /// - /// Constructs an instance of the LanguageService class and uses - /// the given Runspace to execute language service operations. - /// - /// - /// The PowerShellContext in which language service operations will be executed. - /// - /// An ILogger implementation used for writing log messages. - public LanguageService( - PowerShellContext powerShellContext, - ILogger logger) - { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - _powerShellContext = powerShellContext; - _logger = logger; - - _cmdletToAliasDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); - _aliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - _documentSymbolProviders = new IDocumentSymbolProvider[] - { - new ScriptDocumentSymbolProvider(powerShellContext.LocalPowerShellVersion.Version), - new PsdDocumentSymbolProvider(), - new PesterDocumentSymbolProvider() - }; - } - - #endregion - - #region Public Methods - - /// - /// Gets completions for a statement contained in the given - /// script file at the specified line and column position. - /// - /// - /// The script file in which completions will be gathered. - /// - /// - /// The 1-based line number at which completions will be gathered. - /// - /// - /// The 1-based column number at which completions will be gathered. - /// - /// - /// A CommandCompletion instance completions for the identified statement. - /// - public async Task GetCompletionsInFileAsync( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) - { - Validate.IsNotNull(nameof(scriptFile), scriptFile); - - // Get the offset at the specified position. This method - // will also validate the given position. - int fileOffset = - scriptFile.GetOffsetAtPosition( - lineNumber, - columnNumber); - - CommandCompletion commandCompletion = - await AstOperations.GetCompletionsAsync( - scriptFile.ScriptAst, - scriptFile.ScriptTokens, - fileOffset, - _powerShellContext, - _logger, - new CancellationTokenSource(DefaultWaitTimeoutMilliseconds).Token); - - if (commandCompletion == null) - { - return new CompletionResults(); - } - - try - { - CompletionResults completionResults = - CompletionResults.Create( - scriptFile, - commandCompletion); - - // save state of most recent completion - _mostRecentCompletions = completionResults; - _mostRecentRequestFile = scriptFile.Id; - _mostRecentRequestLine = lineNumber; - _mostRecentRequestOffest = columnNumber; - - return completionResults; - } - catch (ArgumentException e) - { - // Bad completion results could return an invalid - // replacement range, catch that here - _logger.Write( - LogLevel.Error, - $"Caught exception while trying to create CompletionResults:\n\n{e.ToString()}"); - - return new CompletionResults(); - } - } - - /// - /// Finds command completion details for the script given a file location - /// - /// The details and contents of a open script file - /// The name of the suggestion that needs details - /// CompletionResult object (contains information about the command completion) - public CompletionDetails GetCompletionDetailsInFile( - ScriptFile file, - string entryName) - { - if (!file.Id.Equals(_mostRecentRequestFile)) - { - return null; - } - - foreach (CompletionDetails completion in _mostRecentCompletions.Completions) - { - if (completion.CompletionText.Equals(entryName)) - { - return completion; - } - } - - // If we found no completions, return null - return null; - } - - /// - /// Finds the symbol in the script given a file location - /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script - /// A SymbolReference of the symbol found at the given location - /// or null if there is no symbol at that location - /// - public SymbolReference FindSymbolAtLocation( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) - { - SymbolReference symbolReference = - AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber); - - if (symbolReference != null) - { - symbolReference.FilePath = scriptFile.FilePath; - } - - return symbolReference; - } - - /// - /// Finds a function definition in the script given a file location - /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script - /// A SymbolReference of the symbol found at the given location - /// or null if there is no symbol at that location - /// - public SymbolReference FindFunctionDefinitionAtLocation( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) - { - SymbolReference symbolReference = - AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber, - includeFunctionDefinitions: true); - - if (symbolReference != null) - { - symbolReference.FilePath = scriptFile.FilePath; - } - - return symbolReference; - } - - /// - /// Finds the details of the symbol at the given script file location. - /// - /// The ScriptFile in which the symbol can be located. - /// The line number at which the symbol can be located. - /// The column number at which the symbol can be located. - /// - public async Task FindSymbolDetailsAtLocationAsync( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) - { - SymbolDetails symbolDetails = null; - SymbolReference symbolReference = - AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber); - - if (symbolReference == null) - { - // TODO #21: Return Result - return null; - } - - symbolReference.FilePath = scriptFile.FilePath; - symbolDetails = - await SymbolDetails.CreateAsync( - symbolReference, - _powerShellContext); - - return symbolDetails; - } - - /// - /// Finds all the symbols in a file. - /// - /// The ScriptFile in which the symbol can be located. - /// - public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile) - { - Validate.IsNotNull(nameof(scriptFile), scriptFile); - - var foundOccurrences = new List(); - foreach (IDocumentSymbolProvider symbolProvider in _documentSymbolProviders) - { - foreach (SymbolReference reference in symbolProvider.ProvideDocumentSymbols(scriptFile)) - { - reference.SourceLine = scriptFile.GetLine(reference.ScriptRegion.StartLineNumber); - reference.FilePath = scriptFile.FilePath; - foundOccurrences.Add(reference); - } - } - - return new FindOccurrencesResult - { - FoundOccurrences = foundOccurrences - }; - } - - /// - /// Finds all the references of a symbol - /// - /// The symbol to find all references for - /// An array of scriptFiles too search for references in - /// The workspace that will be searched for symbols - /// FindReferencesResult - public async Task FindReferencesOfSymbolAsync( - SymbolReference foundSymbol, - ScriptFile[] referencedFiles, - Workspace workspace) - { - if (foundSymbol == null) - { - return null; - } - - int symbolOffset = referencedFiles[0].GetOffsetAtPosition( - foundSymbol.ScriptRegion.StartLineNumber, - foundSymbol.ScriptRegion.StartColumnNumber); - - // Make sure aliases have been loaded - await GetAliasesAsync(); - - // We want to look for references first in referenced files, hence we use ordered dictionary - // TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic - var fileMap = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) - ? new OrderedDictionary() - : new OrderedDictionary(StringComparer.OrdinalIgnoreCase); - - foreach (ScriptFile scriptFile in referencedFiles) - { - fileMap[scriptFile.FilePath] = scriptFile; - } - - foreach (string filePath in workspace.EnumeratePSFiles()) - { - if (!fileMap.Contains(filePath)) - { - if (!workspace.TryGetFile(filePath, out ScriptFile scriptFile)) - { - // If we can't access the file for some reason, just ignore it - continue; - } - - fileMap[filePath] = scriptFile; - } - } - - var symbolReferences = new List(); - foreach (object fileName in fileMap.Keys) - { - var file = (ScriptFile)fileMap[fileName]; - await _aliasHandle.WaitAsync(); - try - { - - IEnumerable references = AstOperations.FindReferencesOfSymbol( - file.ScriptAst, - foundSymbol, - _cmdletToAliasDictionary, - _aliasToCmdletDictionary); - - foreach (SymbolReference reference in references) - { - try - { - reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); - } - catch (ArgumentOutOfRangeException e) - { - reference.SourceLine = string.Empty; - _logger.WriteException("Found reference is out of range in script file", e); - } - reference.FilePath = file.FilePath; - symbolReferences.Add(reference); - } - } - finally - { - _aliasHandle.Release(); - } - } - - return new FindReferencesResult - { - SymbolFileOffset = symbolOffset, - SymbolName = foundSymbol.SymbolName, - FoundReferences = symbolReferences - }; - } - - /// - /// Finds the definition of a symbol in the script file or any of the - /// files that it references. - /// - /// The initial script file to be searched for the symbol's definition. - /// The symbol for which a definition will be found. - /// The Workspace to which the ScriptFile belongs. - /// The resulting GetDefinitionResult for the symbol's definition. - public async Task GetDefinitionOfSymbolAsync( - ScriptFile sourceFile, - SymbolReference foundSymbol, - Workspace workspace) - { - Validate.IsNotNull(nameof(sourceFile), sourceFile); - Validate.IsNotNull(nameof(foundSymbol), foundSymbol); - Validate.IsNotNull(nameof(workspace), workspace); - - ScriptFile[] referencedFiles = - workspace.ExpandScriptReferences( - sourceFile); - - var filesSearched = new HashSet(StringComparer.OrdinalIgnoreCase); - - // look through the referenced files until definition is found - // or there are no more file to look through - SymbolReference foundDefinition = null; - foreach (ScriptFile scriptFile in referencedFiles) - { - foundDefinition = - AstOperations.FindDefinitionOfSymbol( - scriptFile.ScriptAst, - foundSymbol); - - filesSearched.Add(scriptFile.FilePath); - if (foundDefinition != null) - { - foundDefinition.FilePath = scriptFile.FilePath; - break; - } - - if (foundSymbol.SymbolType == SymbolType.Function) - { - // Dot-sourcing is parsed as a "Function" Symbol. - string dotSourcedPath = GetDotSourcedPath(foundSymbol, workspace, scriptFile); - if (scriptFile.FilePath == dotSourcedPath) - { - foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath); - break; - } - } - } - - // if the definition the not found in referenced files - // look for it in all the files in the workspace - if (foundDefinition == null) - { - // Get a list of all powershell files in the workspace path - IEnumerable allFiles = workspace.EnumeratePSFiles(); - foreach (string file in allFiles) - { - if (filesSearched.Contains(file)) - { - continue; - } - - Token[] tokens = null; - ParseError[] parseErrors = null; - foundDefinition = - AstOperations.FindDefinitionOfSymbol( - Parser.ParseFile(file, out tokens, out parseErrors), - foundSymbol); - - filesSearched.Add(file); - if (foundDefinition != null) - { - foundDefinition.FilePath = file; - break; - } - - } - } - - // if definition is not found in file in the workspace - // look for it in the builtin commands - if (foundDefinition == null) - { - CommandInfo cmdInfo = - await CommandHelpers.GetCommandInfoAsync( - foundSymbol.SymbolName, - _powerShellContext); - - foundDefinition = - FindDeclarationForBuiltinCommand( - cmdInfo, - foundSymbol, - workspace); - } - - return foundDefinition != null ? - new GetDefinitionResult(foundDefinition) : - null; - } - - /// - /// Gets a path from a dot-source symbol. - /// - /// The symbol representing the dot-source expression. - /// The current workspace - /// The script file containing the symbol - /// - private static string GetDotSourcedPath(SymbolReference symbol, Workspace workspace, ScriptFile scriptFile) - { - string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"')); - string psScriptRoot = Path.GetDirectoryName(scriptFile.FilePath); - return workspace.ResolveRelativeScriptPath(psScriptRoot, - Regex.Replace(cleanedUpSymbol, @"\$PSScriptRoot|\${PSScriptRoot}", psScriptRoot, RegexOptions.IgnoreCase)); - } - - /// - /// Finds all the occurences of a symbol in the script given a file location - /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script - /// FindOccurrencesResult - public FindOccurrencesResult FindOccurrencesInFile( - ScriptFile file, - int lineNumber, - int columnNumber) - { - SymbolReference foundSymbol = - AstOperations.FindSymbolAtPosition( - file.ScriptAst, - lineNumber, - columnNumber); - - if (foundSymbol == null) - { - return null; - } - - // find all references, and indicate that looking for aliases is not needed - IEnumerable symbolOccurrences = - AstOperations - .FindReferencesOfSymbol( - file.ScriptAst, - foundSymbol, - false); - - return new FindOccurrencesResult - { - FoundOccurrences = symbolOccurrences - }; - } - - /// - /// Finds the parameter set hints of a specific command (determined by a given file location) - /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script - /// ParameterSetSignatures - public async Task FindParameterSetsInFileAsync( - ScriptFile file, - int lineNumber, - int columnNumber) - { - SymbolReference foundSymbol = - AstOperations.FindCommandAtPosition( - file.ScriptAst, - lineNumber, - columnNumber); - - if (foundSymbol == null) - { - return null; - } - - CommandInfo commandInfo = - await CommandHelpers.GetCommandInfoAsync( - foundSymbol.SymbolName, - _powerShellContext); - - if (commandInfo == null) - { - return null; - } - - try - { - IEnumerable commandParamSets = commandInfo.ParameterSets; - return new ParameterSetSignatures(commandParamSets, foundSymbol); - } - catch (RuntimeException e) - { - // A RuntimeException will be thrown when an invalid attribute is - // on a parameter binding block and then that command/script has - // its signatures resolved by typing it into a script. - _logger.WriteException("RuntimeException encountered while accessing command parameter sets", e); - - return null; - } - catch (InvalidOperationException) - { - // For some commands there are no paramsets (like applications). Until - // the valid command types are better understood, catch this exception - // which gets raised when there are no ParameterSets for the command type. - return null; - } - } - - /// - /// Gets the smallest statment ast that contains the given script position as - /// indicated by lineNumber and columnNumber parameters. - /// - /// Open script file. - /// 1-based line number of the position. - /// 1-based column number of the position. - /// - public ScriptRegion FindSmallestStatementAstRegion( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) - { - Ast ast = FindSmallestStatementAst(scriptFile, lineNumber, columnNumber); - if (ast == null) - { - return null; - } - - return ScriptRegion.Create(ast.Extent); - } - - /// - /// Gets the function defined on a given line. - /// - /// Open script file. - /// The 1 based line on which to look for function definition. - /// If found, returns the function definition on the given line. Otherwise, returns null. - public FunctionDefinitionAst GetFunctionDefinitionAtLine( - ScriptFile scriptFile, - int lineNumber) - { - Ast functionDefinitionAst = scriptFile.ScriptAst.Find( - ast => ast is FunctionDefinitionAst && ast.Extent.StartLineNumber == lineNumber, - true); - - return functionDefinitionAst as FunctionDefinitionAst; - } - - /// - /// Finds a function definition that follows or contains the given line number. - /// - /// Open script file. - /// The 1 based line on which to look for function definition. - /// - /// If found, returns the function definition, otherwise, returns null. - public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( - ScriptFile scriptFile, - int lineNumber, - out string helpLocation) - { - // check if the next line contains a function definition - FunctionDefinitionAst funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); - if (funcDefnAst != null) - { - helpLocation = "before"; - return funcDefnAst; - } - - // find all the script definitions that contain the line `lineNumber` - IEnumerable foundAsts = scriptFile.ScriptAst.FindAll( - ast => - { - var fdAst = ast as FunctionDefinitionAst; - if (fdAst == null) - { - return false; - } - - return fdAst.Body.Extent.StartLineNumber < lineNumber && - fdAst.Body.Extent.EndLineNumber > lineNumber; - }, - true); - - if (foundAsts == null || !foundAsts.Any()) - { - helpLocation = null; - return null; - } - - // of all the function definitions found, return the innermost function - // definition that contains `lineNumber` - foreach (FunctionDefinitionAst foundAst in foundAsts.Cast()) - { - if (funcDefnAst == null) - { - funcDefnAst = foundAst; - continue; - } - - if (funcDefnAst.Extent.StartOffset >= foundAst.Extent.StartOffset - && funcDefnAst.Extent.EndOffset <= foundAst.Extent.EndOffset) - { - funcDefnAst = foundAst; - } - } - - // TODO use tokens to check for non empty character instead of just checking for line offset - if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) - { - helpLocation = "begin"; - return funcDefnAst; - } - - if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1) - { - helpLocation = "end"; - return funcDefnAst; - } - - // If we didn't find a function definition, then return null - helpLocation = null; - return null; - } - - #endregion - - #region Private Fields - - /// - /// Gets all aliases found in the runspace - /// - private async Task GetAliasesAsync() - { - if (_areAliasesLoaded) - { - return; - } - - await _aliasHandle.WaitAsync(); - try - { - if (_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - _areAliasesLoaded = true; - return; - } - - var aliases = await _powerShellContext.ExecuteCommandAsync( - new PSCommand() - .AddCommand("Microsoft.PowerShell.Core\\Get-Command") - .AddParameter("CommandType", CommandTypes.Alias), - sendOutputToHost: false, - sendErrorToHost: false); - - foreach (AliasInfo aliasInfo in aliases) - { - // Using Get-Command will obtain aliases from modules not yet loaded, - // these aliases will not have a definition. - if (string.IsNullOrEmpty(aliasInfo.Definition)) - { - continue; - } - - if (!_cmdletToAliasDictionary.ContainsKey(aliasInfo.Definition)) - { - _cmdletToAliasDictionary.Add(aliasInfo.Definition, new List { aliasInfo.Name }); - } - else - { - _cmdletToAliasDictionary[aliasInfo.Definition].Add(aliasInfo.Name); - } - - _aliasToCmdletDictionary.Add(aliasInfo.Name, aliasInfo.Definition); - } - - _areAliasesLoaded = true; - } - catch (PSNotSupportedException e) - { - _logger.Write( - LogLevel.Warning, - $"Caught PSNotSupportedException while attempting to get aliases from remote session:\n\n{e.ToString()}"); - - // Prevent the aliases from being fetched again - no point if the remote doesn't support InvokeCommand. - _areAliasesLoaded = true; - } - catch (TaskCanceledException) - { - // The wait for a RunspaceHandle has timed out, skip aliases for now - } - finally - { - _aliasHandle.Release(); - } - } - - private ScriptFile[] GetBuiltinCommandScriptFiles( - PSModuleInfo moduleInfo, - Workspace workspace) - { - if (moduleInfo == null) - { - return new ScriptFile[0]; - } - - string modPath = moduleInfo.Path; - List scriptFiles = new List(); - ScriptFile newFile; - - // find any files where the moduleInfo's path ends with ps1 or psm1 - // and add it to allowed script files - if (modPath.EndsWith(@".ps1") || modPath.EndsWith(@".psm1")) - { - newFile = workspace.GetFile(modPath); - newFile.IsAnalysisEnabled = false; - scriptFiles.Add(newFile); - } - if (moduleInfo.NestedModules.Count > 0) - { - foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules) - { - string nestedModPath = nestedInfo.Path; - if (nestedModPath.EndsWith(@".ps1") || nestedModPath.EndsWith(@".psm1")) - { - newFile = workspace.GetFile(nestedModPath); - newFile.IsAnalysisEnabled = false; - scriptFiles.Add(newFile); - } - } - } - - return scriptFiles.ToArray(); - } - - private SymbolReference FindDeclarationForBuiltinCommand( - CommandInfo commandInfo, - SymbolReference foundSymbol, - Workspace workspace) - { - if (commandInfo == null) - { - return null; - } - - ScriptFile[] nestedModuleFiles = - GetBuiltinCommandScriptFiles( - commandInfo.Module, - workspace); - - SymbolReference foundDefinition = null; - foreach (ScriptFile nestedModuleFile in nestedModuleFiles) - { - foundDefinition = AstOperations.FindDefinitionOfSymbol( - nestedModuleFile.ScriptAst, - foundSymbol); - - if (foundDefinition != null) - { - foundDefinition.FilePath = nestedModuleFile.FilePath; - break; - } - } - - return foundDefinition; - } - - private Ast FindSmallestStatementAst(ScriptFile scriptFile, int lineNumber, int columnNumber) - { - IEnumerable asts = scriptFile.ScriptAst.FindAll(ast => - { - return ast is StatementAst && ast.Extent.Contains(lineNumber, columnNumber); - }, true); - - // Find the Ast with the smallest extent - Ast minAst = scriptFile.ScriptAst; - foreach (Ast ast in asts) - { - if (ast.Extent.ExtentWidthComparer(minAst.Extent) == -1) - { - minAst = ast; - } - } - - return minAst; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Language/ParameterSetSignatures.cs b/src/PowerShellEditorServices/Language/ParameterSetSignatures.cs deleted file mode 100644 index 68cd8279d..000000000 --- a/src/PowerShellEditorServices/Language/ParameterSetSignatures.cs +++ /dev/null @@ -1,150 +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.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// A class for containing the commandName, the command's - /// possible signatures, and the script extent of the command - /// - public class ParameterSetSignatures - { - #region Properties - /// - /// Gets the name of the command - /// - public string CommandName { get; internal set; } - - /// - /// Gets the collection of signatures for the command - /// - public ParameterSetSignature[] Signatures { get; internal set; } - - /// - /// Gets the script extent of the command - /// - public ScriptRegion ScriptRegion { get; internal set; } - #endregion - - /// - /// Constructs an instance of a ParameterSetSignatures object - /// - /// Collection of parameter set info - /// The SymbolReference of the command - public ParameterSetSignatures(IEnumerable commandInfoSet, SymbolReference foundSymbol) - { - List paramSetSignatures = new List(); - foreach (CommandParameterSetInfo setInfo in commandInfoSet) - { - paramSetSignatures.Add(new ParameterSetSignature(setInfo)); - } - Signatures = paramSetSignatures.ToArray(); - CommandName = foundSymbol.ScriptRegion.Text; - ScriptRegion = foundSymbol.ScriptRegion; - } - } - - /// - /// A class for containing the signature text and the collection of parameters for a signature - /// - public class ParameterSetSignature - { - private static HashSet commonParameterNames = - new HashSet - { - "Verbose", - "Debug", - "ErrorAction", - "WarningAction", - "InformationAction", - "ErrorVariable", - "WarningVariable", - "InformationVariable", - "OutVariable", - "OutBuffer", - "PipelineVariable", - }; - - #region Properties - /// - /// Gets the signature text - /// - public string SignatureText { get; internal set; } - - /// - /// Gets the collection of parameters for the signature - /// - public IEnumerable Parameters { get; internal set; } - #endregion - - /// - /// Constructs an instance of a ParameterSetSignature - /// - /// Collection of parameter info - public ParameterSetSignature(CommandParameterSetInfo commandParamInfoSet) - { - List parameterInfo = new List(); - foreach (CommandParameterInfo commandParameterInfo in commandParamInfoSet.Parameters) - { - if (!commonParameterNames.Contains(commandParameterInfo.Name)) - { - parameterInfo.Add(new ParameterInfo(commandParameterInfo)); - } - } - - SignatureText = commandParamInfoSet.ToString(); - Parameters = parameterInfo.ToArray(); - } - } - - /// - /// A class for containing the parameter info of a parameter - /// - public class ParameterInfo - { - #region Properties - /// - /// Gets the name of the parameter - /// - public string Name { get; internal set; } - - /// - /// Gets the type of the parameter - /// - public string ParameterType { get; internal set; } - - /// - /// Gets the position of the parameter - /// - public int Position { get; internal set; } - - /// - /// Gets a boolean for whetheer or not the parameter is required - /// - public bool IsMandatory { get; internal set; } - - /// - /// Gets the help message of the parameter - /// - public string HelpMessage { get; internal set; } - #endregion - - /// - /// Constructs an instance of a ParameterInfo object - /// - /// Parameter info of the parameter - public ParameterInfo(CommandParameterInfo parameterInfo) - { - this.Name = "-" + parameterInfo.Name; - this.ParameterType = parameterInfo.ParameterType.FullName; - this.Position = parameterInfo.Position; - this.IsMandatory = parameterInfo.IsMandatory; - this.HelpMessage = parameterInfo.HelpMessage; - } - } -} diff --git a/src/PowerShellEditorServices/Language/ScriptExtent.cs b/src/PowerShellEditorServices/Language/ScriptExtent.cs deleted file mode 100644 index 58f3ab84a..000000000 --- a/src/PowerShellEditorServices/Language/ScriptExtent.cs +++ /dev/null @@ -1,109 +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.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides a default IScriptExtent implementation - /// containing details about a section of script content - /// in a file. - /// - public class ScriptExtent : IScriptExtent - { - #region Properties - - /// - /// Gets the file path of the script file in which this extent is contained. - /// - public string File - { - get; - set; - } - - /// - /// Gets or sets the starting column number of the extent. - /// - public int StartColumnNumber - { - get; - set; - } - - /// - /// Gets or sets the starting line number of the extent. - /// - public int StartLineNumber - { - get; - set; - } - - /// - /// Gets or sets the starting file offset of the extent. - /// - public int StartOffset - { - get; - set; - } - - /// - /// Gets or sets the starting script position of the extent. - /// - public IScriptPosition StartScriptPosition - { - get { throw new NotImplementedException(); } - } - /// - /// Gets or sets the text that is contained within the extent. - /// - public string Text - { - get; - set; - } - - /// - /// Gets or sets the ending column number of the extent. - /// - public int EndColumnNumber - { - get; - set; - } - - /// - /// Gets or sets the ending line number of the extent. - /// - public int EndLineNumber - { - get; - set; - } - - /// - /// Gets or sets the ending file offset of the extent. - /// - public int EndOffset - { - get; - set; - } - - /// - /// Gets the ending script position of the extent. - /// - public IScriptPosition EndScriptPosition - { - get { throw new NotImplementedException(); } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Language/SymbolDetails.cs b/src/PowerShellEditorServices/Language/SymbolDetails.cs deleted file mode 100644 index e0971c2ec..000000000 --- a/src/PowerShellEditorServices/Language/SymbolDetails.cs +++ /dev/null @@ -1,95 +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.Diagnostics; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides detailed information for a given symbol. - /// - [DebuggerDisplay("SymbolReference = {SymbolReference.SymbolType}/{SymbolReference.SymbolName}, DisplayString = {DisplayString}")] - public class SymbolDetails - { - #region Properties - - /// - /// Gets the original symbol reference which was used to gather details. - /// - public SymbolReference SymbolReference { get; private set; } - - /// - /// Gets the display string for this symbol. - /// - public string DisplayString { get; private set; } - - /// - /// Gets the documentation string for this symbol. Returns an - /// empty string if the symbol has no documentation. - /// - public string Documentation { get; private set; } - - #endregion - - #region Constructors - - static internal async Task CreateAsync( - SymbolReference symbolReference, - PowerShellContext powerShellContext) - { - SymbolDetails symbolDetails = new SymbolDetails(); - symbolDetails.SymbolReference = symbolReference; - - // If the symbol is a command, get its documentation - if (symbolReference.SymbolType == SymbolType.Function) - { - CommandInfo commandInfo = - await CommandHelpers.GetCommandInfoAsync( - symbolReference.SymbolName, - powerShellContext); - - if (commandInfo != null) - { - symbolDetails.Documentation = - await CommandHelpers.GetCommandSynopsisAsync( - commandInfo, - powerShellContext); - - if (commandInfo.CommandType == CommandTypes.Application) - { - symbolDetails.DisplayString = "(application) " + symbolReference.SymbolName; - } - else - { - symbolDetails.DisplayString = "function " + symbolReference.SymbolName; - } - } - else - { - // Command information can't be loaded. This is likely due to - // the symbol being a function that is defined in a file that - // hasn't been loaded in the runspace yet. - symbolDetails.DisplayString = "function " + symbolReference.SymbolName; - } - } - else if (symbolReference.SymbolType == SymbolType.Parameter) - { - // TODO: Get parameter help - symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName; - } - else if (symbolReference.SymbolType == SymbolType.Variable) - { - symbolDetails.DisplayString = symbolReference.SymbolName; - } - - return symbolDetails; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Language/SymbolReference.cs b/src/PowerShellEditorServices/Language/SymbolReference.cs deleted file mode 100644 index af0551a9e..000000000 --- a/src/PowerShellEditorServices/Language/SymbolReference.cs +++ /dev/null @@ -1,89 +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.Diagnostics; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// A class that holds the type, name, script extent, and source line of a symbol - /// - [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")] - public class SymbolReference - { - #region Properties - - /// - /// Gets the symbol's type - /// - public SymbolType SymbolType { get; private set; } - - /// - /// Gets the name of the symbol - /// - public string SymbolName { get; private set; } - - /// - /// Gets the script extent of the symbol - /// - public ScriptRegion ScriptRegion { get; private set; } - - /// - /// Gets the contents of the line the given symbol is on - /// - public string SourceLine { get; internal set; } - - /// - /// Gets the path of the file in which the symbol was found. - /// - public string FilePath { get; internal set; } - - #endregion - - /// - /// Constructs and instance of a SymbolReference - /// - /// The higher level type of the symbol - /// The name of the symbol - /// The script extent of the symbol - /// The file path of the symbol - /// The line contents of the given symbol (defaults to empty string) - public SymbolReference( - SymbolType symbolType, - string symbolName, - IScriptExtent scriptExtent, - string filePath = "", - string sourceLine = "") - { - // TODO: Verify params - this.SymbolType = symbolType; - this.SymbolName = symbolName; - this.ScriptRegion = ScriptRegion.Create(scriptExtent); - this.FilePath = filePath; - this.SourceLine = sourceLine; - - // TODO: Make sure end column number usage is correct - - // Build the display string - //this.DisplayString = - // string.Format( - // "{0} {1}") - } - - /// - /// Constructs and instance of a SymbolReference - /// - /// The higher level type of the symbol - /// The script extent of the symbol - /// The file path of the symbol - /// The line contents of the given symbol (defaults to empty string) - public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent, string filePath = "", string sourceLine = "") - : this(symbolType, scriptExtent.Text, scriptExtent, filePath, sourceLine) - { - } - } -} diff --git a/src/PowerShellEditorServices/Language/SymbolType.cs b/src/PowerShellEditorServices/Language/SymbolType.cs deleted file mode 100644 index 2dba9a0a0..000000000 --- a/src/PowerShellEditorServices/Language/SymbolType.cs +++ /dev/null @@ -1,48 +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 -{ - /// - /// A way to define symbols on a higher level - /// - public enum SymbolType - { - /// - /// The symbol type is unknown - /// - Unknown = 0, - - /// - /// The symbol is a vairable - /// - Variable, - - /// - /// The symbol is a function - /// - Function, - - /// - /// The symbol is a parameter - /// - Parameter, - - /// - /// The symbol is a DSC configuration - /// - Configuration, - - /// - /// The symbol is a workflow - /// - Workflow, - - /// - /// The symbol is a hashtable key - /// - HashtableKey - } -} diff --git a/src/PowerShellEditorServices/Language/TokenOperations.cs b/src/PowerShellEditorServices/Language/TokenOperations.cs deleted file mode 100644 index e62dca68c..000000000 --- a/src/PowerShellEditorServices/Language/TokenOperations.cs +++ /dev/null @@ -1,215 +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.Management.Automation.Language; -using System.Text.RegularExpressions; - -namespace Microsoft.PowerShell.EditorServices -{ - - /// - /// Provides common operations for the tokens of a parsed script. - /// - internal static class TokenOperations - { - // Region kinds to align with VSCode's region kinds - private const string RegionKindComment = "comment"; - private const string RegionKindRegion = "region"; - private const string RegionKindNone = null; - - // These regular expressions are used to match lines which mark the start and end of region comment in a PowerShell - // script. They are based on the defaults in the VS Code Language Configuration at; - // https://github.com/Microsoft/vscode/blob/64186b0a26/extensions/powershell/language-configuration.json#L26-L31 - // https://github.com/Microsoft/vscode/issues/49070 - static private readonly Regex s_startRegionTextRegex = new Regex( - @"^\s*#[rR]egion\b", RegexOptions.Compiled); - static private readonly Regex s_endRegionTextRegex = new Regex( - @"^\s*#[eE]nd[rR]egion\b", RegexOptions.Compiled); - - /// - /// Extracts all of the unique foldable regions in a script given the list tokens - /// - internal static FoldingReferenceList FoldableReferences( - Token[] tokens) - { - var refList = new FoldingReferenceList(); - - Stack tokenCurlyStack = new Stack(); - Stack tokenParenStack = new Stack(); - foreach (Token token in tokens) - { - switch (token.Kind) - { - // Find matching braces { -> } - // Find matching hashes @{ -> } - case TokenKind.LCurly: - case TokenKind.AtCurly: - tokenCurlyStack.Push(token); - break; - - case TokenKind.RCurly: - if (tokenCurlyStack.Count > 0) - { - refList.SafeAdd(CreateFoldingReference(tokenCurlyStack.Pop(), token, RegionKindNone)); - } - break; - - // Find matching parentheses ( -> ) - // Find matching array literals @( -> ) - // Find matching subexpressions $( -> ) - case TokenKind.LParen: - case TokenKind.AtParen: - case TokenKind.DollarParen: - tokenParenStack.Push(token); - break; - - case TokenKind.RParen: - if (tokenParenStack.Count > 0) - { - refList.SafeAdd(CreateFoldingReference(tokenParenStack.Pop(), token, RegionKindNone)); - } - break; - - // Find contiguous here strings @' -> '@ - // Find unopinionated variable names ${ \n \n } - // Find contiguous expandable here strings @" -> "@ - case TokenKind.HereStringLiteral: - case TokenKind.Variable: - case TokenKind.HereStringExpandable: - if (token.Extent.StartLineNumber != token.Extent.EndLineNumber) - { - refList.SafeAdd(CreateFoldingReference(token, token, RegionKindNone)); - } - break; - } - } - - // Find matching comment regions #region -> #endregion - // Given a list of tokens, find the tokens that are comments and - // the comment text is either `#region` or `#endregion`, and then use a stack to determine - // the ranges they span - // - // Find blocks of line comments # comment1\n# comment2\n... - // Finding blocks of comment tokens is more complicated as the newline characters are not - // classed as comments. To workaround this we search for valid block comments (See IsBlockCmment) - // and then determine contiguous line numbers from there - // - // Find comments regions <# -> #> - // Match the token start and end of kind TokenKind.Comment - var tokenCommentRegionStack = new Stack(); - Token blockStartToken = null; - int blockNextLine = -1; - - for (int index = 0; index < tokens.Length; index++) - { - Token token = tokens[index]; - if (token.Kind != TokenKind.Comment) { continue; } - - // Processing for comment regions <# -> #> - if (token.Extent.StartLineNumber != token.Extent.EndLineNumber) - { - refList.SafeAdd(CreateFoldingReference(token, token, RegionKindComment)); - continue; - } - - if (!IsBlockComment(index, tokens)) { continue; } - - // Regex's are very expensive. Use them sparingly! - // Processing for #region -> #endregion - if (s_startRegionTextRegex.IsMatch(token.Text)) - { - tokenCommentRegionStack.Push(token); - continue; - } - if (s_endRegionTextRegex.IsMatch(token.Text)) - { - // Mismatched regions in the script can cause bad stacks. - if (tokenCommentRegionStack.Count > 0) - { - refList.SafeAdd(CreateFoldingReference(tokenCommentRegionStack.Pop(), token, RegionKindRegion)); - } - continue; - } - - // If it's neither a start or end region then it could be block line comment - // Processing for blocks of line comments # comment1\n# comment2\n... - int thisLine = token.Extent.StartLineNumber - 1; - if ((blockStartToken != null) && (thisLine != blockNextLine)) - { - refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment)); - blockStartToken = token; - } - if (blockStartToken == null) { blockStartToken = token; } - blockNextLine = thisLine + 1; - } - - // If we exit the token array and we're still processing comment lines, then the - // comment block simply ends at the end of document - if (blockStartToken != null) - { - refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment)); - } - - return refList; - } - - /// - /// Creates an instance of a FoldingReference object from a start and end langauge Token - /// Returns null if the line range is invalid - /// - static private FoldingReference CreateFoldingReference( - Token startToken, - Token endToken, - string matchKind) - { - if (endToken.Extent.EndLineNumber == startToken.Extent.StartLineNumber) { return null; } - // Extents are base 1, but LSP is base 0, so minus 1 off all lines and character positions - return new FoldingReference { - StartLine = startToken.Extent.StartLineNumber - 1, - StartCharacter = startToken.Extent.StartColumnNumber - 1, - EndLine = endToken.Extent.EndLineNumber - 1, - EndCharacter = endToken.Extent.EndColumnNumber - 1, - Kind = matchKind - }; - } - - /// - /// Creates an instance of a FoldingReference object from a start token and an end line - /// Returns null if the line range is invalid - /// - static private FoldingReference CreateFoldingReference( - Token startToken, - int endLine, - string matchKind) - { - if (endLine == (startToken.Extent.StartLineNumber - 1)) { return null; } - // Extents are base 1, but LSP is base 0, so minus 1 off all lines and character positions - return new FoldingReference { - StartLine = startToken.Extent.StartLineNumber - 1, - StartCharacter = startToken.Extent.StartColumnNumber - 1, - EndLine = endLine, - EndCharacter = 0, - Kind = matchKind - }; - } - - /// - /// Returns true if a Token is a block comment; - /// - Must be a TokenKind.comment - /// - Must be preceeded by TokenKind.NewLine - /// - Token text must start with a '#'.false This is because comment regions - /// start with '<#' but have the same TokenKind - /// - static private bool IsBlockComment(int index, Token[] tokens) { - Token thisToken = tokens[index]; - if (thisToken.Kind != TokenKind.Comment) { return false; } - if (index == 0) { return true; } - if (tokens[index - 1].Kind != TokenKind.NewLine) { return false; } - return thisToken.Text.StartsWith("#"); - } - } -} diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj deleted file mode 100644 index fd282a96d..000000000 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - PowerShell Editor Services - Provides common PowerShell editor capabilities as a .NET library. - netstandard2.0 - Microsoft.PowerShell.EditorServices - Latest - - - - 1591,1573,1572 - bin\$(TargetFramework)\$(Configuration)\Microsoft.PowerShell.EditorServices.xml - - - - - - - - - - - - - - - $(DefineConstants);RELEASE - - diff --git a/src/PowerShellEditorServices/Properties/AssemblyInfo.cs b/src/PowerShellEditorServices/Properties/AssemblyInfo.cs deleted file mode 100644 index d7cc1beb5..000000000 --- a/src/PowerShellEditorServices/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +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.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.PowerShell.EditorServices.Protocol")] -[assembly: InternalsVisibleTo("Microsoft.PowerShell.EditorServices.Test")] -[assembly: InternalsVisibleTo("Microsoft.PowerShell.EditorServices.Test.Shared")] - diff --git a/src/PowerShellEditorServices/Providers/FeatureProviderBase.cs b/src/PowerShellEditorServices/Providers/FeatureProviderBase.cs deleted file mode 100644 index ecf8e8ce2..000000000 --- a/src/PowerShellEditorServices/Providers/FeatureProviderBase.cs +++ /dev/null @@ -1,19 +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 -{ - /// - /// Provides a base implementation of IFeatureProvider. - /// - public abstract class FeatureProviderBase : IFeatureProvider - { - /// - /// Gets the provider class type's FullName as the - /// ProviderId. - /// - public string ProviderId => this.GetType().FullName; - } -} diff --git a/src/PowerShellEditorServices/Providers/FeatureProviderCollection.cs b/src/PowerShellEditorServices/Providers/FeatureProviderCollection.cs deleted file mode 100644 index fd4e1b1c9..000000000 --- a/src/PowerShellEditorServices/Providers/FeatureProviderCollection.cs +++ /dev/null @@ -1,46 +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; -using System.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides a default implementation of IFeatureProviderCollection. - /// - public class FeatureProviderCollection : IFeatureProviderCollection - where TProvider : IFeatureProvider - { - #region Private Fields - - private List providerList = new List(); - - #endregion - - #region IFeatureProviderCollection Implementation - - void IFeatureProviderCollection.Add(TProvider provider) - { - if (!this.providerList.Contains(provider)) - { - this.providerList.Add(provider); - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.providerList.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.providerList.GetEnumerator(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Providers/IFeatureProvider.cs b/src/PowerShellEditorServices/Providers/IFeatureProvider.cs deleted file mode 100644 index bea42b821..000000000 --- a/src/PowerShellEditorServices/Providers/IFeatureProvider.cs +++ /dev/null @@ -1,19 +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 -{ - /// - /// Defines the contract for a feature provider, particularly for provider identification. - /// - public interface IFeatureProvider - { - /// - /// Specifies a unique identifier for the feature provider, typically a - /// fully-qualified name like "Microsoft.PowerShell.EditorServices.MyProvider" - /// - string ProviderId { get; } - } -} diff --git a/src/PowerShellEditorServices/Providers/IFeatureProviderCollection.cs b/src/PowerShellEditorServices/Providers/IFeatureProviderCollection.cs deleted file mode 100644 index 29351a4a4..000000000 --- a/src/PowerShellEditorServices/Providers/IFeatureProviderCollection.cs +++ /dev/null @@ -1,22 +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.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Defines the contract for a collection of provider implementations. - /// - public interface IFeatureProviderCollection : IEnumerable - where TProvider : IFeatureProvider - { - /// - /// Adds a provider to the collection. - /// - /// The provider to be added. - void Add(TProvider provider); - } -} diff --git a/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs deleted file mode 100644 index 998b58a79..000000000 --- a/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs +++ /dev/null @@ -1,165 +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.Linq; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Session.Capabilities -{ - using Microsoft.PowerShell.EditorServices.Utility; - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Management.Automation; - - internal class DscBreakpointCapability : IRunspaceCapability - { - private string[] dscResourceRootPaths = new string[0]; - - private Dictionary breakpointsPerFile = - new Dictionary(); - - public async Task> SetLineBreakpointsAsync( - PowerShellContext powerShellContext, - string scriptPath, - BreakpointDetails[] breakpoints) - { - List resultBreakpointDetails = - new List(); - - // We always get the latest array of breakpoint line numbers - // so store that for future use - if (breakpoints.Length > 0) - { - // Set the breakpoints for this scriptPath - this.breakpointsPerFile[scriptPath] = - breakpoints.Select(b => b.LineNumber).ToArray(); - } - else - { - // No more breakpoints for this scriptPath, remove it - this.breakpointsPerFile.Remove(scriptPath); - } - - string hashtableString = - string.Join( - ", ", - this.breakpointsPerFile - .Select(file => $"@{{Path=\"{file.Key}\";Line=@({string.Join(",", file.Value)})}}")); - - // Run Enable-DscDebug as a script because running it as a PSCommand - // causes an error which states that the Breakpoint parameter has not - // been passed. - await powerShellContext.ExecuteScriptStringAsync( - hashtableString.Length > 0 - ? $"Enable-DscDebug -Breakpoint {hashtableString}" - : "Disable-DscDebug", - false, - false); - - // Verify all the breakpoints and return them - foreach (var breakpoint in breakpoints) - { - breakpoint.Verified = true; - } - - return breakpoints.ToList(); - } - - public bool IsDscResourcePath(string scriptPath) - { - return dscResourceRootPaths.Any( - dscResourceRootPath => - scriptPath.StartsWith( - dscResourceRootPath, - StringComparison.CurrentCultureIgnoreCase)); - } - - public static DscBreakpointCapability CheckForCapability( - RunspaceDetails runspaceDetails, - PowerShellContext powerShellContext, - ILogger logger) - { - DscBreakpointCapability capability = null; - - // DSC support is enabled only for Windows PowerShell. - if ((runspaceDetails.PowerShellVersion.Version.Major < 6) && - (runspaceDetails.Context != RunspaceContext.DebuggedRunspace)) - { - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspaceDetails.Runspace; - - // Attempt to import the updated DSC module - powerShell.AddCommand("Import-Module"); - powerShell.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1"); - powerShell.AddParameter("PassThru"); - powerShell.AddParameter("ErrorAction", "Ignore"); - - PSObject moduleInfo = null; - - try - { - moduleInfo = powerShell.Invoke().FirstOrDefault(); - } - catch (RuntimeException e) - { - logger.WriteException("Could not load the DSC module!", e); - } - - if (moduleInfo != null) - { - logger.Write(LogLevel.Verbose, "Side-by-side DSC module found, gathering DSC resource paths..."); - - // The module was loaded, add the breakpoint capability - capability = new DscBreakpointCapability(); - runspaceDetails.AddCapability(capability); - - powerShell.Commands.Clear(); - powerShell.AddScript("Write-Host \"Gathering DSC resource paths, this may take a while...\""); - powerShell.Invoke(); - - // Get the list of DSC resource paths - powerShell.Commands.Clear(); - powerShell.AddCommand("Get-DscResource"); - powerShell.AddCommand("Select-Object"); - powerShell.AddParameter("ExpandProperty", "ParentPath"); - - Collection resourcePaths = null; - - try - { - resourcePaths = powerShell.Invoke(); - } - catch (CmdletInvocationException e) - { - logger.WriteException("Get-DscResource failed!", e); - } - - if (resourcePaths != null) - { - capability.dscResourceRootPaths = - resourcePaths - .Select(o => (string)o.BaseObject) - .ToArray(); - - logger.Write(LogLevel.Verbose, $"DSC resources found: {resourcePaths.Count}"); - } - else - { - logger.Write(LogLevel.Verbose, $"No DSC resources found."); - } - } - else - { - logger.Write(LogLevel.Verbose, $"Side-by-side DSC module was not found."); - } - } - } - - return capability; - } - } -} diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs deleted file mode 100644 index 8bc751a9f..000000000 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ /dev/null @@ -1,192 +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 Microsoft.PowerShell.EditorServices.Components; -using Microsoft.PowerShell.EditorServices.Console; -using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Templates; -using Microsoft.PowerShell.EditorServices.Utility; -using System.IO; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Manages a single session for all editor services. This - /// includes managing all open script files for the session. - /// - public class EditorSession - { - #region Private Fields - - private ILogger logger; - - #endregion - - #region Properties - - /// - /// Gets the IHostInput implementation to use for this session. - /// - public IHostInput HostInput { get; private set; } - - /// - /// Gets the Workspace instance for this session. - /// - public Workspace Workspace { get; private set; } - - /// - /// Gets the PowerShellContext instance for this session. - /// - public PowerShellContext PowerShellContext { get; private set; } - - /// - /// Gets the LanguageService instance for this session. - /// - public LanguageService LanguageService { get; private set; } - - /// - /// Gets the AnalysisService instance for this session. - /// - public AnalysisService AnalysisService { get; private set; } - - /// - /// Gets the DebugService instance for this session. - /// - public DebugService DebugService { get; private set; } - - /// - /// Gets the ExtensionService instance for this session. - /// - public ExtensionService ExtensionService { get; private set; } - - /// - /// Gets the TemplateService instance for this session. - /// - public TemplateService TemplateService { get; private set; } - - /// - /// Gets the RemoteFileManager instance for this session. - /// - public RemoteFileManager RemoteFileManager { get; private set; } - - /// - /// Gets the IComponentCollection instance for this session. - /// - public IComponentRegistry Components { get; } = new ComponentRegistry(); - - #endregion - - #region Constructors - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public EditorSession(ILogger logger) - { - this.logger = logger; - } - - #endregion - - #region Public Methods - - /// - /// Starts the session using the provided IConsoleHost implementation - /// for the ConsoleService. - /// - /// - /// - public void StartSession( - PowerShellContext powerShellContext, - IHostInput hostInput) - { - this.PowerShellContext = powerShellContext; - this.HostInput = hostInput; - - // Initialize all services - this.LanguageService = new LanguageService(this.PowerShellContext, this.logger); - this.ExtensionService = new ExtensionService(this.PowerShellContext); - this.TemplateService = new TemplateService(this.PowerShellContext, this.logger); - - this.InstantiateAnalysisService(); - - // Create a workspace to contain open files - this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version, this.logger); - } - - /// - /// Starts a debug-only session using the provided IConsoleHost implementation - /// for the ConsoleService. - /// - /// - /// - /// - /// An IEditorOperations implementation used to interact with the editor. - /// - public void StartDebugSession( - PowerShellContext powerShellContext, - IHostInput hostInput, - IEditorOperations editorOperations) - { - this.PowerShellContext = powerShellContext; - this.HostInput = hostInput; - - // Initialize all services - this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations, logger); - this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager, logger); - - // Create a workspace to contain open files - this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version, this.logger); - } - - /// - /// Starts the DebugService if it's not already strated - /// - /// - /// An IEditorOperations implementation used to interact with the editor. - /// - public void StartDebugService(IEditorOperations editorOperations) - { - if (this.DebugService == null) - { - this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations, logger); - this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager, logger); - } - } - - internal void InstantiateAnalysisService(string settingsPath = null) - { - // Create the analysis service. If this fails, the result will be null -- any exceptions are caught and logged - this.AnalysisService = AnalysisService.Create(settingsPath, this.logger); - } - - #endregion - - #region IDisposable Implementation - - /// - /// Disposes of any Runspaces that were created for the - /// services used in this session. - /// - public void Dispose() - { - if (this.AnalysisService != null) - { - this.AnalysisService.Dispose(); - this.AnalysisService = null; - } - - if (this.PowerShellContext != null) - { - this.PowerShellContext.Dispose(); - this.PowerShellContext = null; - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/ExecutionOptions.cs b/src/PowerShellEditorServices/Session/ExecutionOptions.cs deleted file mode 100644 index a1071606f..000000000 --- a/src/PowerShellEditorServices/Session/ExecutionOptions.cs +++ /dev/null @@ -1,92 +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 -{ - /// - /// Defines options for the execution of a command. - /// - public class ExecutionOptions - { - private bool? _shouldExecuteInOriginalRunspace; - - #region Properties - - /// - /// Gets or sets a boolean that determines whether command output - /// should be written to the host. - /// - public bool WriteOutputToHost { get; set; } - - /// - /// Gets or sets a boolean that determines whether command errors - /// should be written to the host. - /// - public bool WriteErrorsToHost { get; set; } - - /// - /// Gets or sets a boolean that determines whether the executed - /// command should be added to the command history. - /// - public bool AddToHistory { get; set; } - - /// - /// Gets or sets a boolean that determines whether the execution - /// of the command should interrupt the command prompt. Should - /// only be set if WriteOutputToHost is false but the command - /// should still interrupt the command prompt. - /// - public bool InterruptCommandPrompt { get; set; } - - /// - /// Gets or sets a value indicating whether the text of the command - /// should be written to the host as if it was ran interactively. - /// - public bool WriteInputToHost { get; set; } - - /// - /// Gets or sets a value indicating whether the command to - /// be executed is a console input prompt, such as the - /// PSConsoleHostReadLine function. - /// - internal bool IsReadLine { get; set; } - - /// - /// Gets or sets a value indicating whether the command should - /// be invoked in the original runspace. In the majority of cases - /// this should remain unset. - /// - internal bool ShouldExecuteInOriginalRunspace - { - get - { - return _shouldExecuteInOriginalRunspace ?? IsReadLine; - } - set - { - _shouldExecuteInOriginalRunspace = value; - } - } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ExecutionOptions class with - /// default settings configured. - /// - public ExecutionOptions() - { - this.WriteOutputToHost = true; - this.WriteErrorsToHost = true; - this.WriteInputToHost = false; - this.AddToHistory = false; - this.InterruptCommandPrompt = false; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/ExecutionStatus.cs b/src/PowerShellEditorServices/Session/ExecutionStatus.cs deleted file mode 100644 index 233d0499e..000000000 --- a/src/PowerShellEditorServices/Session/ExecutionStatus.cs +++ /dev/null @@ -1,39 +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 -{ - /// - /// Enumerates the possible execution results that can occur after - /// executing a command or script. - /// - public enum ExecutionStatus - { - /// - /// Indicates that execution has not yet started. - /// - Pending, - - /// - /// Indicates that the command is executing. - /// - Running, - - /// - /// Indicates that execution has failed. - /// - Failed, - - /// - /// Indicates that execution was aborted by the user. - /// - Aborted, - - /// - /// Indicates that execution completed successfully. - /// - Completed - } -} diff --git a/src/PowerShellEditorServices/Session/ExecutionStatusChangedEventArgs.cs b/src/PowerShellEditorServices/Session/ExecutionStatusChangedEventArgs.cs deleted file mode 100644 index cd2dcaaf2..000000000 --- a/src/PowerShellEditorServices/Session/ExecutionStatusChangedEventArgs.cs +++ /dev/null @@ -1,52 +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 -{ - /// - /// Contains details about an executed - /// - public class ExecutionStatusChangedEventArgs - { - #region Properties - - /// - /// Gets the options used when the command was executed. - /// - public ExecutionOptions ExecutionOptions { get; private set; } - - /// - /// Gets the command execution's current status. - /// - public ExecutionStatus ExecutionStatus { get; private set; } - - /// - /// If true, the command execution had errors. - /// - public bool HadErrors { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ExecutionStatusChangedEventArgs class. - /// - /// The command execution's current status. - /// The options used when the command was executed. - /// If execution has completed, indicates whether there were errors. - public ExecutionStatusChangedEventArgs( - ExecutionStatus executionStatus, - ExecutionOptions executionOptions, - bool hadErrors) - { - this.ExecutionStatus = executionStatus; - this.ExecutionOptions = executionOptions; - this.HadErrors = hadErrors || (executionStatus == ExecutionStatus.Failed); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/ExecutionTarget.cs b/src/PowerShellEditorServices/Session/ExecutionTarget.cs deleted file mode 100644 index 70ec3cb6f..000000000 --- a/src/PowerShellEditorServices/Session/ExecutionTarget.cs +++ /dev/null @@ -1,28 +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.Session -{ - /// - /// Represents the different API's available for executing commands. - /// - internal enum ExecutionTarget - { - /// - /// Indicates that the command should be invoked through the PowerShell debugger. - /// - Debugger, - - /// - /// Indicates that the command should be invoked via an instance of the PowerShell class. - /// - PowerShell, - - /// - /// Indicates that the command should be invoked through the PowerShell engine's event manager. - /// - InvocationEvent - } -} diff --git a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHost.cs b/src/PowerShellEditorServices/Session/Host/EditorServicesPSHost.cs deleted file mode 100644 index 7c32acdb3..000000000 --- a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHost.cs +++ /dev/null @@ -1,372 +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 Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides an implementation of the PSHost class for the - /// ConsoleService and routes its calls to an IConsoleHost - /// implementation. - /// - public class EditorServicesPSHost : PSHost, IHostSupportsInteractiveSession - { - #region Private Fields - - private ILogger Logger; - private HostDetails hostDetails; - private Guid instanceId = Guid.NewGuid(); - private EditorServicesPSHostUserInterface hostUserInterface; - private IHostSupportsInteractiveSession hostSupportsInteractiveSession; - private PowerShellContext powerShellContext; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHost class - /// with the given IConsoleHost implementation. - /// - /// - /// An implementation of IHostSupportsInteractiveSession for runspace management. - /// - /// - /// Provides details about the host application. - /// - /// - /// The EditorServicesPSHostUserInterface implementation to use for this host. - /// - /// An ILogger implementation to use for this host. - public EditorServicesPSHost( - PowerShellContext powerShellContext, - HostDetails hostDetails, - EditorServicesPSHostUserInterface hostUserInterface, - ILogger logger) - { - this.Logger = logger; - this.hostDetails = hostDetails; - this.hostUserInterface = hostUserInterface; - this.hostSupportsInteractiveSession = powerShellContext; - this.powerShellContext = powerShellContext; - } - - #endregion - - #region PSHost Implementation - - /// - /// - /// - public override Guid InstanceId - { - get { return this.instanceId; } - } - - /// - /// - /// - public override string Name - { - get { return this.hostDetails.Name; } - } - - internal class ConsoleColorProxy - { - private EditorServicesPSHostUserInterface _hostUserInterface; - - internal ConsoleColorProxy(EditorServicesPSHostUserInterface hostUserInterface) - { - if (hostUserInterface == null) throw new ArgumentNullException("hostUserInterface"); - _hostUserInterface = hostUserInterface; - } - - /// - /// The ForegroundColor for Error - /// - public ConsoleColor ErrorForegroundColor - { - get - { return _hostUserInterface.ErrorForegroundColor; } - set - { _hostUserInterface.ErrorForegroundColor = value; } - } - - /// - /// The BackgroundColor for Error - /// - public ConsoleColor ErrorBackgroundColor - { - get - { return _hostUserInterface.ErrorBackgroundColor; } - set - { _hostUserInterface.ErrorBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Warning - /// - public ConsoleColor WarningForegroundColor - { - get - { return _hostUserInterface.WarningForegroundColor; } - set - { _hostUserInterface.WarningForegroundColor = value; } - } - - /// - /// The BackgroundColor for Warning - /// - public ConsoleColor WarningBackgroundColor - { - get - { return _hostUserInterface.WarningBackgroundColor; } - set - { _hostUserInterface.WarningBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Debug - /// - public ConsoleColor DebugForegroundColor - { - get - { return _hostUserInterface.DebugForegroundColor; } - set - { _hostUserInterface.DebugForegroundColor = value; } - } - - /// - /// The BackgroundColor for Debug - /// - public ConsoleColor DebugBackgroundColor - { - get - { return _hostUserInterface.DebugBackgroundColor; } - set - { _hostUserInterface.DebugBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Verbose - /// - public ConsoleColor VerboseForegroundColor - { - get - { return _hostUserInterface.VerboseForegroundColor; } - set - { _hostUserInterface.VerboseForegroundColor = value; } - } - - /// - /// The BackgroundColor for Verbose - /// - public ConsoleColor VerboseBackgroundColor - { - get - { return _hostUserInterface.VerboseBackgroundColor; } - set - { _hostUserInterface.VerboseBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Progress - /// - public ConsoleColor ProgressForegroundColor - { - get - { return _hostUserInterface.ProgressForegroundColor; } - set - { _hostUserInterface.ProgressForegroundColor = value; } - } - - /// - /// The BackgroundColor for Progress - /// - public ConsoleColor ProgressBackgroundColor - { - get - { return _hostUserInterface.ProgressBackgroundColor; } - set - { _hostUserInterface.ProgressBackgroundColor = value; } - } - } - - /// - /// Return the actual console host object so that the user can get at - /// the unproxied methods. - /// - public override PSObject PrivateData - { - get - { - if (hostUserInterface == null) return null; - return _consoleColorProxy ?? (_consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(hostUserInterface))); - } - } - private PSObject _consoleColorProxy; - - /// - /// - /// - public override Version Version - { - get { return this.hostDetails.Version; } - } - - // TODO: Pull these from IConsoleHost - - /// - /// - /// - public override System.Globalization.CultureInfo CurrentCulture - { - get { return System.Globalization.CultureInfo.CurrentCulture; } - } - - /// - /// - /// - public override System.Globalization.CultureInfo CurrentUICulture - { - get { return System.Globalization.CultureInfo.CurrentUICulture; } - } - - /// - /// - /// - public override PSHostUserInterface UI - { - get { return this.hostUserInterface; } - } - - /// - /// - /// - public override void EnterNestedPrompt() - { - this.powerShellContext.EnterNestedPrompt(); - } - - /// - /// - /// - public override void ExitNestedPrompt() - { - this.powerShellContext.ExitNestedPrompt(); - } - - /// - /// - /// - public override void NotifyBeginApplication() - { - Logger.Write(LogLevel.Verbose, "NotifyBeginApplication() called."); - this.hostUserInterface.IsNativeApplicationRunning = true; - } - - /// - /// - /// - public override void NotifyEndApplication() - { - Logger.Write(LogLevel.Verbose, "NotifyEndApplication() called."); - this.hostUserInterface.IsNativeApplicationRunning = false; - } - - /// - /// - /// - /// - public override void SetShouldExit(int exitCode) - { - if (this.IsRunspacePushed) - { - this.PopRunspace(); - } - } - - #endregion - - #region IHostSupportsInteractiveSession Implementation - - /// - /// - /// - /// - public bool IsRunspacePushed - { - get - { - if (this.hostSupportsInteractiveSession != null) - { - return this.hostSupportsInteractiveSession.IsRunspacePushed; - } - else - { - throw new NotImplementedException(); - } - } - } - - /// - /// - /// - /// - public Runspace Runspace - { - get - { - if (this.hostSupportsInteractiveSession != null) - { - return this.hostSupportsInteractiveSession.Runspace; - } - else - { - throw new NotImplementedException(); - } - } - } - - /// - /// - /// - /// - public void PushRunspace(Runspace runspace) - { - if (this.hostSupportsInteractiveSession != null) - { - this.hostSupportsInteractiveSession.PushRunspace(runspace); - } - else - { - throw new NotImplementedException(); - } - } - - /// - /// - /// - public void PopRunspace() - { - if (this.hostSupportsInteractiveSession != null) - { - this.hostSupportsInteractiveSession.PopRunspace(); - } - else - { - throw new NotImplementedException(); - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHostUserInterface.cs b/src/PowerShellEditorServices/Session/Host/EditorServicesPSHostUserInterface.cs deleted file mode 100644 index 24ea1c8db..000000000 --- a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHostUserInterface.cs +++ /dev/null @@ -1,1070 +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.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Linq; -using System.Security; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Console; -using System.Threading; -using Microsoft.PowerShell.EditorServices.Utility; -using Microsoft.PowerShell.EditorServices.Session; -using System.Globalization; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides an implementation of the PSHostUserInterface class - /// for the ConsoleService and routes its calls to an IConsoleHost - /// implementation. - /// - public abstract class EditorServicesPSHostUserInterface : - PSHostUserInterface, - IHostInput, - IHostOutput, - IHostUISupportsMultipleChoiceSelection - { - #region Private Fields - - private readonly ConcurrentDictionary currentProgressMessages = - new ConcurrentDictionary(); - private PromptHandler activePromptHandler; - private PSHostRawUserInterface rawUserInterface; - private CancellationTokenSource commandLoopCancellationToken; - - /// - /// The PowerShellContext to use for executing commands. - /// - protected PowerShellContext powerShellContext; - - #endregion - - #region Public Constants - - /// - /// Gets a const string for the console's debug message prefix. - /// - public const string DebugMessagePrefix = "DEBUG: "; - - /// - /// Gets a const string for the console's warning message prefix. - /// - public const string WarningMessagePrefix = "WARNING: "; - - /// - /// Gets a const string for the console's verbose message prefix. - /// - public const string VerboseMessagePrefix = "VERBOSE: "; - - #endregion - - #region Properties - -#if !PowerShellv3 && !PowerShellv4 && !PowerShellv5r1 // Only available in Windows 10 Update 1 or higher - /// - /// Returns true if the host supports VT100 output codes. - /// - public override bool SupportsVirtualTerminal => true; -#endif - - /// - /// Returns true if a native application is currently running. - /// - public bool IsNativeApplicationRunning { get; internal set; } - - private bool IsCommandLoopRunning { get; set; } - - /// - /// Gets the ILogger implementation used for this host. - /// - protected ILogger Logger { get; private set; } - - /// - /// Gets a value indicating whether writing progress is supported. - /// - internal protected virtual bool SupportsWriteProgress => false; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHostUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The PowerShellContext to use for executing commands. - /// The PSHostRawUserInterface implementation to use for this host. - /// An ILogger implementation to use for this host. - public EditorServicesPSHostUserInterface( - PowerShellContext powerShellContext, - PSHostRawUserInterface rawUserInterface, - ILogger logger) - { - this.Logger = logger; - this.powerShellContext = powerShellContext; - this.rawUserInterface = rawUserInterface; - - this.powerShellContext.DebuggerStop += PowerShellContext_DebuggerStop; - this.powerShellContext.DebuggerResumed += PowerShellContext_DebuggerResumed; - this.powerShellContext.ExecutionStatusChanged += PowerShellContext_ExecutionStatusChanged; - } - - #endregion - - #region Public Methods - - /// - /// Starts the host's interactive command loop. - /// - public void StartCommandLoop() - { - if (!this.IsCommandLoopRunning) - { - this.IsCommandLoopRunning = true; - this.ShowCommandPrompt(); - } - } - - /// - /// Stops the host's interactive command loop. - /// - public void StopCommandLoop() - { - if (this.IsCommandLoopRunning) - { - this.IsCommandLoopRunning = false; - this.CancelCommandPrompt(); - } - } - - private void ShowCommandPrompt() - { - if (this.commandLoopCancellationToken == null) - { - this.commandLoopCancellationToken = new CancellationTokenSource(); - - var commandLoopThreadTask = - Task.Factory.StartNew( - async () => - { - await this.StartReplLoopAsync(this.commandLoopCancellationToken.Token); - }); - } - else - { - Logger.Write(LogLevel.Verbose, "StartReadLoop called while read loop is already running"); - } - } - - private void CancelCommandPrompt() - { - if (this.commandLoopCancellationToken != null) - { - // Set this to false so that Ctrl+C isn't trapped by any - // lingering ReadKey - // TOOD: Move this to Terminal impl! - //Console.TreatControlCAsInput = false; - - this.commandLoopCancellationToken.Cancel(); - this.commandLoopCancellationToken = null; - } - } - - /// - /// Cancels the currently executing command or prompt. - /// - public void SendControlC() - { - if (this.activePromptHandler != null) - { - this.activePromptHandler.CancelPrompt(); - } - else - { - // Cancel the current execution - this.powerShellContext.AbortExecution(); - } - } - - #endregion - - #region Abstract Methods - - /// - /// Requests that the HostUI implementation read a command line - /// from the user to be executed in the integrated console command - /// loop. - /// - /// - /// A CancellationToken used to cancel the command line request. - /// - /// A Task that can be awaited for the resulting input string. - protected abstract Task ReadCommandLineAsync(CancellationToken cancellationToken); - - /// - /// Creates an InputPrompt handle to use for displaying input - /// prompts to the user. - /// - /// A new InputPromptHandler instance. - protected abstract InputPromptHandler OnCreateInputPromptHandler(); - - /// - /// Creates a ChoicePromptHandler to use for displaying a - /// choice prompt to the user. - /// - /// A new ChoicePromptHandler instance. - protected abstract ChoicePromptHandler OnCreateChoicePromptHandler(); - - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - public abstract void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor); - - /// - /// Sends a progress update event to the user. - /// - /// The source ID of the progress event. - /// The details of the activity's current progress. - protected abstract void UpdateProgress( - long sourceId, - ProgressDetails progressDetails); - - #endregion - - #region IHostInput Implementation - - #endregion - - #region PSHostUserInterface Implementation - - /// - /// - /// - /// - /// - /// - /// - public override Dictionary Prompt( - string promptCaption, - string promptMessage, - Collection fieldDescriptions) - { - FieldDetails[] fields = - fieldDescriptions - .Select(f => { return FieldDetails.Create(f, this.Logger); }) - .ToArray(); - - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - Task> promptTask = - this.CreateInputPromptHandler() - .PromptForInputAsync( - promptCaption, - promptMessage, - fields, - cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "Prompt", - cancellationToken); - - // Convert all values to PSObjects - var psObjectDict = new Dictionary(); - - // The result will be null if the prompt was cancelled - if (promptTask.Result != null) - { - // Convert all values to PSObjects - foreach (var keyValuePair in promptTask.Result) - { - psObjectDict.Add( - keyValuePair.Key, - keyValuePair.Value != null - ? PSObject.AsPSObject(keyValuePair.Value) - : null); - } - } - - // Return the result - return psObjectDict; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public override int PromptForChoice( - string promptCaption, - string promptMessage, - Collection choiceDescriptions, - int defaultChoice) - { - ChoiceDetails[] choices = - choiceDescriptions - .Select(ChoiceDetails.Create) - .ToArray(); - - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - Task promptTask = - this.CreateChoicePromptHandler() - .PromptForChoiceAsync( - promptCaption, - promptMessage, - choices, - defaultChoice, - cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "PromptForChoice", - cancellationToken); - - // Return the result - return promptTask.Result; - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public override PSCredential PromptForCredential( - string promptCaption, - string promptMessage, - string userName, - string targetName, - PSCredentialTypes allowedCredentialTypes, - PSCredentialUIOptions options) - { - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - Task> promptTask = - this.CreateInputPromptHandler() - .PromptForInputAsync( - promptCaption, - promptMessage, - new FieldDetails[] { new CredentialFieldDetails("Credential", "Credential", userName) }, - cancellationToken.Token); - - Task unpackTask = - promptTask.ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - // Return the value of the sole field - return (PSCredential)task.Result?["Credential"]; - }); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - unpackTask, - "PromptForCredential", - cancellationToken); - - return unpackTask.Result; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public override PSCredential PromptForCredential( - string caption, - string message, - string userName, - string targetName) - { - return this.PromptForCredential( - caption, - message, - userName, - targetName, - PSCredentialTypes.Default, - PSCredentialUIOptions.Default); - } - - /// - /// - /// - /// - public override PSHostRawUserInterface RawUI - { - get { return this.rawUserInterface; } - } - - /// - /// - /// - /// - public override string ReadLine() - { - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - Task promptTask = - this.CreateInputPromptHandler() - .PromptForInputAsync(cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "ReadLine", - cancellationToken); - - return promptTask.Result; - } - - /// - /// - /// - /// - public override SecureString ReadLineAsSecureString() - { - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - Task promptTask = - this.CreateInputPromptHandler() - .PromptForSecureInputAsync(cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "ReadLineAsSecureString", - cancellationToken); - - return promptTask.Result; - } - - /// - /// - /// - /// - /// - /// - public override void Write( - ConsoleColor foregroundColor, - ConsoleColor backgroundColor, - string value) - { - this.WriteOutput( - value, - false, - OutputType.Normal, - foregroundColor, - backgroundColor); - } - - /// - /// - /// - /// - public override void Write(string value) - { - this.WriteOutput( - value, - false, - OutputType.Normal, - this.rawUserInterface.ForegroundColor, - this.rawUserInterface.BackgroundColor); - } - - /// - /// - /// - /// - public override void WriteLine(string value) - { - this.WriteOutput( - value, - true, - OutputType.Normal, - this.rawUserInterface.ForegroundColor, - this.rawUserInterface.BackgroundColor); - } - - /// - /// - /// - /// - public override void WriteDebugLine(string message) - { - this.WriteOutput( - DebugMessagePrefix + message, - true, - OutputType.Debug, - foregroundColor: this.DebugForegroundColor, - backgroundColor: this.DebugBackgroundColor); - } - - /// - /// - /// - /// - public override void WriteVerboseLine(string message) - { - this.WriteOutput( - VerboseMessagePrefix + message, - true, - OutputType.Verbose, - foregroundColor: this.VerboseForegroundColor, - backgroundColor: this.VerboseBackgroundColor); - } - - /// - /// - /// - /// - public override void WriteWarningLine(string message) - { - this.WriteOutput( - WarningMessagePrefix + message, - true, - OutputType.Warning, - foregroundColor: this.WarningForegroundColor, - backgroundColor: this.WarningBackgroundColor); - } - - /// - /// - /// - /// - public override void WriteErrorLine(string value) - { - this.WriteOutput( - value, - true, - OutputType.Error, - foregroundColor: this.ErrorForegroundColor, - backgroundColor: this.ErrorBackgroundColor); - } - - /// - /// Invoked by to display a progress record. - /// - /// - /// Unique identifier of the source of the record. An int64 is used because typically, - /// the 'this' pointer of the command from whence the record is originating is used, and - /// that may be from a remote Runspace on a 64-bit machine. - /// - /// - /// The record being reported to the host. - /// - public sealed override void WriteProgress( - long sourceId, - ProgressRecord record) - { - // Maintain old behavior if this isn't overridden. - if (!this.SupportsWriteProgress) - { - this.UpdateProgress(sourceId, ProgressDetails.Create(record)); - return; - } - - // Keep a list of progress records we write so we can automatically - // clean them up after the pipeline ends. - if (record.RecordType == ProgressRecordType.Completed) - { - this.currentProgressMessages.TryRemove(new ProgressKey(sourceId, record), out _); - } - else - { - // Adding with a value of null here because we don't actually need a dictionary. We're - // only using ConcurrentDictionary<,> becuase there is no ConcurrentHashSet<>. - this.currentProgressMessages.TryAdd(new ProgressKey(sourceId, record), null); - } - - this.WriteProgressImpl(sourceId, record); - } - - /// - /// Invoked by to display a progress record. - /// - /// - /// Unique identifier of the source of the record. An int64 is used because typically, - /// the 'this' pointer of the command from whence the record is originating is used, and - /// that may be from a remote Runspace on a 64-bit machine. - /// - /// - /// The record being reported to the host. - /// - protected virtual void WriteProgressImpl(long sourceId, ProgressRecord record) - { - } - - internal void ClearProgress() - { - const string nonEmptyString = "noop"; - if (!this.SupportsWriteProgress) - { - return; - } - - foreach (ProgressKey key in this.currentProgressMessages.Keys) - { - // This constructor throws if the activity description is empty even - // with completed records. - var record = new ProgressRecord( - key.ActivityId, - activity: nonEmptyString, - statusDescription: nonEmptyString); - - record.RecordType = ProgressRecordType.Completed; - this.WriteProgressImpl(key.SourceId, record); - } - - this.currentProgressMessages.Clear(); - } - - #endregion - - #region IHostUISupportsMultipleChoiceSelection Implementation - - /// - /// - /// - /// - /// - /// - /// - /// - public Collection PromptForChoice( - string promptCaption, - string promptMessage, - Collection choiceDescriptions, - IEnumerable defaultChoices) - { - ChoiceDetails[] choices = - choiceDescriptions - .Select(ChoiceDetails.Create) - .ToArray(); - - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - Task promptTask = - this.CreateChoicePromptHandler() - .PromptForChoiceAsync( - promptCaption, - promptMessage, - choices, - defaultChoices.ToArray(), - cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "PromptForChoice", - cancellationToken); - - // Return the result - return new Collection(promptTask.Result.ToList()); - } - - #endregion - - #region Private Methods - - private Coordinates lastPromptLocation; - - private async Task WritePromptStringToHostAsync(CancellationToken cancellationToken) - { - try - { - if (this.lastPromptLocation != null && - this.lastPromptLocation.X == await ConsoleProxy.GetCursorLeftAsync(cancellationToken) && - this.lastPromptLocation.Y == await ConsoleProxy.GetCursorTopAsync(cancellationToken)) - { - return; - } - } - // When output is redirected (like when running tests) attempting to get - // the cursor position will throw. - catch (System.IO.IOException) - { - } - - PSCommand promptCommand = new PSCommand().AddScript("prompt"); - - cancellationToken.ThrowIfCancellationRequested(); - string promptString = - (await this.powerShellContext.ExecuteCommandAsync(promptCommand, false, false)) - .Select(pso => pso.BaseObject) - .OfType() - .FirstOrDefault() ?? "PS> "; - - // Add the [DBG] prefix if we're stopped in the debugger and the prompt doesn't already have [DBG] in it - if (this.powerShellContext.IsDebuggerStopped && !promptString.Contains("[DBG]")) - { - promptString = - string.Format( - CultureInfo.InvariantCulture, - "[DBG]: {0}", - promptString); - } - - // Update the stored prompt string if the session is remote - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote) - { - promptString = - string.Format( - CultureInfo.InvariantCulture, - "[{0}]: {1}", - this.powerShellContext.CurrentRunspace.Runspace.ConnectionInfo != null - ? this.powerShellContext.CurrentRunspace.Runspace.ConnectionInfo.ComputerName - : this.powerShellContext.CurrentRunspace.SessionDetails.ComputerName, - promptString); - } - - cancellationToken.ThrowIfCancellationRequested(); - - // Write the prompt string - this.WriteOutput(promptString, false); - this.lastPromptLocation = new Coordinates( - await ConsoleProxy.GetCursorLeftAsync(cancellationToken), - await ConsoleProxy.GetCursorTopAsync(cancellationToken)); - } - - private void WriteDebuggerBanner(DebuggerStopEventArgs eventArgs) - { - // TODO: What do we display when we don't know why we stopped? - - if (eventArgs.Breakpoints.Count > 0) - { - // The breakpoint classes have nice ToString output so use that - this.WriteOutput( - Environment.NewLine + $"Hit {eventArgs.Breakpoints[0].ToString()}\n", - true, - OutputType.Normal, - ConsoleColor.Blue); - } - } - - internal static ConsoleColor BackgroundColor { get; set; } - - internal ConsoleColor ErrorForegroundColor { get; set; } = ConsoleColor.Red; - internal ConsoleColor ErrorBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor WarningForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor WarningBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor DebugForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor DebugBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor VerboseForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor VerboseBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor ProgressForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor ProgressBackgroundColor { get; set; } = ConsoleColor.DarkCyan; - - private async Task StartReplLoopAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - string commandString = null; - int originalCursorTop = 0; - - try - { - await this.WritePromptStringToHostAsync(cancellationToken); - } - catch (OperationCanceledException) - { - break; - } - - try - { - originalCursorTop = await ConsoleProxy.GetCursorTopAsync(cancellationToken); - commandString = await this.ReadCommandLineAsync(cancellationToken); - } - catch (PipelineStoppedException) - { - this.WriteOutput( - "^C", - true, - OutputType.Normal, - foregroundColor: ConsoleColor.Red); - } - // Do nothing here, the while loop condition will exit. - catch (TaskCanceledException) - { } - catch (OperationCanceledException) - { } - catch (Exception e) // Narrow this if possible - { - this.WriteOutput( - $"\n\nAn error occurred while reading input:\n\n{e.ToString()}\n", - true, - OutputType.Error); - - Logger.WriteException("Caught exception while reading command line", e); - } - finally - { - if (!cancellationToken.IsCancellationRequested && - originalCursorTop == await ConsoleProxy.GetCursorTopAsync(cancellationToken)) - { - this.WriteLine(); - } - } - - if (!string.IsNullOrWhiteSpace(commandString)) - { - var unusedTask = - this.powerShellContext - .ExecuteScriptStringAsync( - commandString, - writeInputToHost: false, - writeOutputToHost: true, - addToHistory: true) - .ConfigureAwait(continueOnCapturedContext: false); - - break; - } - } - } - - private InputPromptHandler CreateInputPromptHandler() - { - if (this.activePromptHandler != null) - { - Logger.Write( - LogLevel.Error, - "Prompt handler requested while another prompt is already active."); - } - - InputPromptHandler inputPromptHandler = this.OnCreateInputPromptHandler(); - this.activePromptHandler = inputPromptHandler; - this.activePromptHandler.PromptCancelled += activePromptHandler_PromptCancelled; - - return inputPromptHandler; - } - - private ChoicePromptHandler CreateChoicePromptHandler() - { - if (this.activePromptHandler != null) - { - Logger.Write( - LogLevel.Error, - "Prompt handler requested while another prompt is already active."); - } - - ChoicePromptHandler choicePromptHandler = this.OnCreateChoicePromptHandler(); - this.activePromptHandler = choicePromptHandler; - this.activePromptHandler.PromptCancelled += activePromptHandler_PromptCancelled; - - return choicePromptHandler; - } - - private void activePromptHandler_PromptCancelled(object sender, EventArgs e) - { - // Clean up the existing prompt - this.activePromptHandler.PromptCancelled -= activePromptHandler_PromptCancelled; - this.activePromptHandler = null; - } - private void WaitForPromptCompletion( - Task promptTask, - string promptFunctionName, - CancellationTokenSource cancellationToken) - { - try - { - // This will synchronously block on the prompt task - // method which gets run on another thread. - promptTask.Wait(); - - if (promptTask.Status == TaskStatus.WaitingForActivation) - { - // The Wait() call has timed out, cancel the prompt - cancellationToken.Cancel(); - - this.WriteOutput("\r\nPrompt has been cancelled due to a timeout.\r\n"); - throw new PipelineStoppedException(); - } - } - catch (AggregateException e) - { - // Find the right InnerException - Exception innerException = e.InnerException; - while (innerException is AggregateException) - { - innerException = innerException.InnerException; - } - - // Was the task cancelled? - if (innerException is TaskCanceledException) - { - // Stop the pipeline if the prompt was cancelled - throw new PipelineStoppedException(); - } - else if (innerException is PipelineStoppedException) - { - // The prompt is being cancelled, rethrow the exception - throw innerException; - } - else - { - // Rethrow the exception - throw new Exception( - string.Format( - "{0} failed, check inner exception for details", - promptFunctionName), - innerException); - } - } - } - - private void PowerShellContext_DebuggerStop(object sender, System.Management.Automation.DebuggerStopEventArgs e) - { - if (!this.IsCommandLoopRunning) - { - StartCommandLoop(); - return; - } - - // Cancel any existing prompt first - this.CancelCommandPrompt(); - - this.WriteDebuggerBanner(e); - this.ShowCommandPrompt(); - } - - private void PowerShellContext_DebuggerResumed(object sender, System.Management.Automation.DebuggerResumeAction e) - { - this.CancelCommandPrompt(); - } - - private void PowerShellContext_ExecutionStatusChanged(object sender, ExecutionStatusChangedEventArgs eventArgs) - { - // The command loop should only be manipulated if it's already started - if (eventArgs.ExecutionStatus == ExecutionStatus.Aborted) - { - this.ClearProgress(); - - // When aborted, cancel any lingering prompts - if (this.activePromptHandler != null) - { - this.activePromptHandler.CancelPrompt(); - this.WriteOutput(string.Empty); - } - } - else if ( - eventArgs.ExecutionOptions.WriteOutputToHost || - eventArgs.ExecutionOptions.InterruptCommandPrompt) - { - // Any command which writes output to the host will affect - // the display of the prompt - if (eventArgs.ExecutionStatus != ExecutionStatus.Running) - { - this.ClearProgress(); - - // Execution has completed, start the input prompt - this.ShowCommandPrompt(); - StartCommandLoop(); - } - else - { - // A new command was started, cancel the input prompt - StopCommandLoop(); - this.CancelCommandPrompt(); - } - } - else if ( - eventArgs.ExecutionOptions.WriteErrorsToHost && - (eventArgs.ExecutionStatus == ExecutionStatus.Failed || - eventArgs.HadErrors)) - { - this.ClearProgress(); - this.WriteOutput(string.Empty, true); - var unusedTask = this.WritePromptStringToHostAsync(CancellationToken.None); - } - } - - #endregion - - private readonly struct ProgressKey : IEquatable - { - internal readonly long SourceId; - - internal readonly int ActivityId; - - internal readonly int ParentActivityId; - - internal ProgressKey(long sourceId, ProgressRecord record) - { - SourceId = sourceId; - ActivityId = record.ActivityId; - ParentActivityId = record.ParentActivityId; - } - - public bool Equals(ProgressKey other) - { - return SourceId == other.SourceId - && ActivityId == other.ActivityId - && ParentActivityId == other.ParentActivityId; - } - - public override int GetHashCode() - { - // Algorithm from https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations - unchecked - { - int hash = 17; - hash = hash * 31 + SourceId.GetHashCode(); - hash = hash * 31 + ActivityId.GetHashCode(); - hash = hash * 31 + ParentActivityId.GetHashCode(); - return hash; - } - } - } - } -} diff --git a/src/PowerShellEditorServices/Session/Host/IHostInput.cs b/src/PowerShellEditorServices/Session/Host/IHostInput.cs deleted file mode 100644 index 28c79839d..000000000 --- a/src/PowerShellEditorServices/Session/Host/IHostInput.cs +++ /dev/null @@ -1,28 +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 -{ - /// - /// Provides methods for integrating with the host's input system. - /// - public interface IHostInput - { - /// - /// Starts the host's interactive command loop. - /// - void StartCommandLoop(); - - /// - /// Stops the host's interactive command loop. - /// - void StopCommandLoop(); - - /// - /// Cancels the currently executing command or prompt. - /// - void SendControlC(); - } -} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Session/Host/IHostOutput.cs b/src/PowerShellEditorServices/Session/Host/IHostOutput.cs deleted file mode 100644 index 4f36bc54f..000000000 --- a/src/PowerShellEditorServices/Session/Host/IHostOutput.cs +++ /dev/null @@ -1,175 +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 -{ - /// - /// Provides a simplified interface for writing output to a - /// PowerShell host implementation. - /// - public interface IHostOutput - { - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor); - } - - /// - /// Provides helpful extension methods for the IHostOutput interface. - /// - public static class IHostOutputExtensions - { - /// - /// Writes normal output with a newline to the user interface. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString) - { - hostOutput.WriteOutput(outputString, true); - } - - /// - /// Writes normal output to the user interface. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - bool includeNewLine) - { - hostOutput.WriteOutput( - outputString, - includeNewLine, - OutputType.Normal); - } - - /// - /// Writes output of a particular type to the user interface - /// with a newline ending. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// Specifies the type of output to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - OutputType outputType) - { - hostOutput.WriteOutput( - outputString, - true, - OutputType.Normal); - } - - /// - /// Writes output of a particular type to the user interface. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - bool includeNewLine, - OutputType outputType) - { - hostOutput.WriteOutput( - outputString, - includeNewLine, - outputType, - ConsoleColor.Gray, - (ConsoleColor)(-1)); // -1 indicates the console's raw background color - } - - /// - /// Writes output of a particular type to the user interface using - /// a particular foreground color. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor) - { - hostOutput.WriteOutput( - outputString, - includeNewLine, - outputType, - foregroundColor, - (ConsoleColor)(-1)); // -1 indicates the console's raw background color - } - } -} diff --git a/src/PowerShellEditorServices/Session/Host/SimplePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Session/Host/SimplePSHostRawUserInterface.cs deleted file mode 100644 index 89895a776..000000000 --- a/src/PowerShellEditorServices/Session/Host/SimplePSHostRawUserInterface.cs +++ /dev/null @@ -1,230 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Management.Automation.Host; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides an simple implementation of the PSHostRawUserInterface class. - /// - public class SimplePSHostRawUserInterface : PSHostRawUserInterface - { - #region Private Fields - - private const int DefaultConsoleHeight = 100; - private const int DefaultConsoleWidth = 120; - - private ILogger Logger; - - private Size currentBufferSize = new Size(DefaultConsoleWidth, DefaultConsoleHeight); - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the SimplePSHostRawUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The ILogger implementation to use for this instance. - public SimplePSHostRawUserInterface(ILogger logger) - { - this.Logger = logger; - this.ForegroundColor = ConsoleColor.White; - this.BackgroundColor = ConsoleColor.Black; - } - - #endregion - - #region PSHostRawUserInterface Implementation - - /// - /// Gets or sets the background color of the console. - /// - public override ConsoleColor BackgroundColor - { - get; - set; - } - - /// - /// Gets or sets the foreground color of the console. - /// - public override ConsoleColor ForegroundColor - { - get; - set; - } - - /// - /// Gets or sets the size of the console buffer. - /// - public override Size BufferSize - { - get - { - return this.currentBufferSize; - } - set - { - this.currentBufferSize = value; - } - } - - /// - /// Gets or sets the cursor's position in the console buffer. - /// - public override Coordinates CursorPosition - { - get; - set; - } - - /// - /// Gets or sets the size of the cursor in the console buffer. - /// - public override int CursorSize - { - get; - set; - } - - /// - /// Gets or sets the position of the console's window. - /// - public override Coordinates WindowPosition - { - get; - set; - } - - /// - /// Gets or sets the size of the console's window. - /// - public override Size WindowSize - { - get; - set; - } - - /// - /// Gets or sets the console window's title. - /// - public override string WindowTitle - { - get; - set; - } - - /// - /// Gets a boolean that determines whether a keypress is available. - /// - public override bool KeyAvailable - { - get { return false; } - } - - /// - /// Gets the maximum physical size of the console window. - /// - public override Size MaxPhysicalWindowSize - { - get { return new Size(DefaultConsoleWidth, DefaultConsoleHeight); } - } - - /// - /// Gets the maximum size of the console window. - /// - public override Size MaxWindowSize - { - get { return new Size(DefaultConsoleWidth, DefaultConsoleHeight); } - } - - /// - /// Reads the current key pressed in the console. - /// - /// Options for reading the current keypress. - /// A KeyInfo struct with details about the current keypress. - public override KeyInfo ReadKey(ReadKeyOptions options) - { - Logger.Write( - LogLevel.Warning, - "PSHostRawUserInterface.ReadKey was called"); - - throw new System.NotImplementedException(); - } - - /// - /// Flushes the current input buffer. - /// - public override void FlushInputBuffer() - { - Logger.Write( - LogLevel.Warning, - "PSHostRawUserInterface.FlushInputBuffer was called"); - } - - /// - /// Gets the contents of the console buffer in a rectangular area. - /// - /// The rectangle inside which buffer contents will be accessed. - /// A BufferCell array with the requested buffer contents. - public override BufferCell[,] GetBufferContents(Rectangle rectangle) - { - return new BufferCell[0,0]; - } - - /// - /// Scrolls the contents of the console buffer. - /// - /// The source rectangle to scroll. - /// The destination coordinates by which to scroll. - /// The rectangle inside which the scrolling will be clipped. - /// The cell with which the buffer will be filled. - public override void ScrollBufferContents( - Rectangle source, - Coordinates destination, - Rectangle clip, - BufferCell fill) - { - Logger.Write( - LogLevel.Warning, - "PSHostRawUserInterface.ScrollBufferContents was called"); - } - - /// - /// Sets the contents of the buffer inside the specified rectangle. - /// - /// The rectangle inside which buffer contents will be filled. - /// The BufferCell which will be used to fill the requested space. - public override void SetBufferContents( - Rectangle rectangle, - BufferCell fill) - { - Logger.Write( - LogLevel.Warning, - "PSHostRawUserInterface.SetBufferContents was called"); - } - - /// - /// Sets the contents of the buffer at the given coordinate. - /// - /// The coordinate at which the buffer will be changed. - /// The new contents for the buffer at the given coordinate. - public override void SetBufferContents( - Coordinates origin, - BufferCell[,] contents) - { - Logger.Write( - LogLevel.Warning, - "PSHostRawUserInterface.SetBufferContents was called"); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/Host/TerminalPSHostRawUserInterface.cs b/src/PowerShellEditorServices/Session/Host/TerminalPSHostRawUserInterface.cs deleted file mode 100644 index be217c9d9..000000000 --- a/src/PowerShellEditorServices/Session/Host/TerminalPSHostRawUserInterface.cs +++ /dev/null @@ -1,331 +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 Microsoft.PowerShell.EditorServices.Console; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Threading; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides an implementation of the PSHostRawUserInterface class - /// for the ConsoleService and routes its calls to an IConsoleHost - /// implementation. - /// - internal class TerminalPSHostRawUserInterface : PSHostRawUserInterface - { - #region Private Fields - - private readonly PSHostRawUserInterface internalRawUI; - private ILogger Logger; - private KeyInfo? lastKeyDown; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the TerminalPSHostRawUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The ILogger implementation to use for this instance. - /// The InternalHost instance from the origin runspace. - public TerminalPSHostRawUserInterface(ILogger logger, PSHost internalHost) - { - this.Logger = logger; - this.internalRawUI = internalHost.UI.RawUI; - } - - #endregion - - #region PSHostRawUserInterface Implementation - - /// - /// Gets or sets the background color of the console. - /// - public override ConsoleColor BackgroundColor - { - get { return System.Console.BackgroundColor; } - set { System.Console.BackgroundColor = value; } - } - - /// - /// Gets or sets the foreground color of the console. - /// - public override ConsoleColor ForegroundColor - { - get { return System.Console.ForegroundColor; } - set { System.Console.ForegroundColor = value; } - } - - /// - /// Gets or sets the size of the console buffer. - /// - public override Size BufferSize - { - get => this.internalRawUI.BufferSize; - set => this.internalRawUI.BufferSize = value; - } - - /// - /// Gets or sets the cursor's position in the console buffer. - /// - public override Coordinates CursorPosition - { - get - { - return new Coordinates( - ConsoleProxy.GetCursorLeft(), - ConsoleProxy.GetCursorTop()); - } - - set => this.internalRawUI.CursorPosition = value; - } - - /// - /// Gets or sets the size of the cursor in the console buffer. - /// - public override int CursorSize - { - get => this.internalRawUI.CursorSize; - set => this.internalRawUI.CursorSize = value; - } - - /// - /// Gets or sets the position of the console's window. - /// - public override Coordinates WindowPosition - { - get => this.internalRawUI.WindowPosition; - set => this.internalRawUI.WindowPosition = value; - } - - /// - /// Gets or sets the size of the console's window. - /// - public override Size WindowSize - { - get => this.internalRawUI.WindowSize; - set => this.internalRawUI.WindowSize = value; - } - - /// - /// Gets or sets the console window's title. - /// - public override string WindowTitle - { - get => this.internalRawUI.WindowTitle; - set => this.internalRawUI.WindowTitle = value; - } - - /// - /// Gets a boolean that determines whether a keypress is available. - /// - public override bool KeyAvailable => this.internalRawUI.KeyAvailable; - - /// - /// Gets the maximum physical size of the console window. - /// - public override Size MaxPhysicalWindowSize => this.internalRawUI.MaxPhysicalWindowSize; - - /// - /// Gets the maximum size of the console window. - /// - public override Size MaxWindowSize => this.internalRawUI.MaxWindowSize; - - /// - /// Reads the current key pressed in the console. - /// - /// Options for reading the current keypress. - /// A KeyInfo struct with details about the current keypress. - public override KeyInfo ReadKey(ReadKeyOptions options) - { - - bool includeUp = (options & ReadKeyOptions.IncludeKeyUp) != 0; - - // Key Up was requested and we have a cached key down we can return. - if (includeUp && this.lastKeyDown != null) - { - KeyInfo info = this.lastKeyDown.Value; - this.lastKeyDown = null; - return new KeyInfo( - info.VirtualKeyCode, - info.Character, - info.ControlKeyState, - keyDown: false); - } - - bool intercept = (options & ReadKeyOptions.NoEcho) != 0; - bool includeDown = (options & ReadKeyOptions.IncludeKeyDown) != 0; - if (!(includeDown || includeUp)) - { - throw new PSArgumentException( - "Cannot read key options. To read options, set one or both of the following: IncludeKeyDown, IncludeKeyUp.", - nameof(options)); - } - - // Allow ControlC as input so we can emulate pipeline stop requests. We can't actually - // determine if a stop is requested without using non-public API's. - bool oldValue = System.Console.TreatControlCAsInput; - try - { - System.Console.TreatControlCAsInput = true; - ConsoleKeyInfo key = ConsoleProxy.ReadKey(intercept, default(CancellationToken)); - - if (IsCtrlC(key)) - { - // Caller wants CtrlC as input so return it. - if ((options & ReadKeyOptions.AllowCtrlC) != 0) - { - return ProcessKey(key, includeDown); - } - - // Caller doesn't want CtrlC so throw a PipelineStoppedException to emulate - // a real stop. This will not show an exception to a script based caller and it - // will avoid having to return something like default(KeyInfo). - throw new PipelineStoppedException(); - } - - return ProcessKey(key, includeDown); - } - finally - { - System.Console.TreatControlCAsInput = oldValue; - } - } - - /// - /// Flushes the current input buffer. - /// - public override void FlushInputBuffer() - { - Logger.Write( - LogLevel.Warning, - "PSHostRawUserInterface.FlushInputBuffer was called"); - } - - /// - /// Gets the contents of the console buffer in a rectangular area. - /// - /// The rectangle inside which buffer contents will be accessed. - /// A BufferCell array with the requested buffer contents. - public override BufferCell[,] GetBufferContents(Rectangle rectangle) - { - return this.internalRawUI.GetBufferContents(rectangle); - } - - /// - /// Scrolls the contents of the console buffer. - /// - /// The source rectangle to scroll. - /// The destination coordinates by which to scroll. - /// The rectangle inside which the scrolling will be clipped. - /// The cell with which the buffer will be filled. - public override void ScrollBufferContents( - Rectangle source, - Coordinates destination, - Rectangle clip, - BufferCell fill) - { - this.internalRawUI.ScrollBufferContents(source, destination, clip, fill); - } - - /// - /// Sets the contents of the buffer inside the specified rectangle. - /// - /// The rectangle inside which buffer contents will be filled. - /// The BufferCell which will be used to fill the requested space. - public override void SetBufferContents( - Rectangle rectangle, - BufferCell fill) - { - // If the rectangle is all -1s then it means clear the visible buffer - if (rectangle.Top == -1 && - rectangle.Bottom == -1 && - rectangle.Left == -1 && - rectangle.Right == -1) - { - System.Console.Clear(); - return; - } - - this.internalRawUI.SetBufferContents(rectangle, fill); - } - - /// - /// Sets the contents of the buffer at the given coordinate. - /// - /// The coordinate at which the buffer will be changed. - /// The new contents for the buffer at the given coordinate. - public override void SetBufferContents( - Coordinates origin, - BufferCell[,] contents) - { - this.internalRawUI.SetBufferContents(origin, contents); - } - - #endregion - - /// - /// Determines if a key press represents the input Ctrl + C. - /// - /// The key to test. - /// - /// if the key represents the input Ctrl + C, - /// otherwise . - /// - private static bool IsCtrlC(ConsoleKeyInfo keyInfo) - { - // In the VSCode terminal Ctrl C is processed as virtual key code "3", which - // is not a named value in the ConsoleKey enum. - if ((int)keyInfo.Key == 3) - { - return true; - } - - return keyInfo.Key == ConsoleKey.C && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0; - } - - /// - /// Converts objects to objects and caches - /// key down events for the next key up request. - /// - /// The key to convert. - /// - /// A value indicating whether the result should be a key down event. - /// - /// The converted value. - private KeyInfo ProcessKey(ConsoleKeyInfo key, bool isDown) - { - // Translate ConsoleModifiers to ControlKeyStates - ControlKeyStates states = default; - if ((key.Modifiers & ConsoleModifiers.Alt) != 0) - { - states |= ControlKeyStates.LeftAltPressed; - } - - if ((key.Modifiers & ConsoleModifiers.Control) != 0) - { - states |= ControlKeyStates.LeftCtrlPressed; - } - - if ((key.Modifiers & ConsoleModifiers.Shift) != 0) - { - states |= ControlKeyStates.ShiftPressed; - } - - var result = new KeyInfo((int)key.Key, key.KeyChar, states, isDown); - if (isDown) - { - this.lastKeyDown = result; - } - - return result; - } - } -} diff --git a/src/PowerShellEditorServices/Session/Host/TerminalPSHostUserInterface.cs b/src/PowerShellEditorServices/Session/Host/TerminalPSHostUserInterface.cs deleted file mode 100644 index 82dab2d1e..000000000 --- a/src/PowerShellEditorServices/Session/Host/TerminalPSHostUserInterface.cs +++ /dev/null @@ -1,180 +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 Microsoft.PowerShell.EditorServices.Console; - -namespace Microsoft.PowerShell.EditorServices -{ - using System; - using System.Management.Automation; - using System.Management.Automation.Host; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.PowerShell.EditorServices.Utility; - - /// - /// Provides an EditorServicesPSHostUserInterface implementation - /// that integrates with the user's terminal UI. - /// - public class TerminalPSHostUserInterface : EditorServicesPSHostUserInterface - { - #region Private Fields - - private readonly PSHostUserInterface internalHostUI; - private ConsoleReadLine consoleReadLine; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHostUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The PowerShellContext to use for executing commands. - /// An ILogger implementation to use for this host. - /// The InternalHost instance from the origin runspace. - public TerminalPSHostUserInterface( - PowerShellContext powerShellContext, - ILogger logger, - PSHost internalHost) - : base( - powerShellContext, - new TerminalPSHostRawUserInterface(logger, internalHost), - logger) - { - this.internalHostUI = internalHost.UI; - this.consoleReadLine = new ConsoleReadLine(powerShellContext); - - // Set the output encoding to UTF-8 so that special - // characters are written to the console correctly - System.Console.OutputEncoding = System.Text.Encoding.UTF8; - - System.Console.CancelKeyPress += - (obj, args) => - { - if (!this.IsNativeApplicationRunning) - { - // We'll handle Ctrl+C - args.Cancel = true; - this.SendControlC(); - } - }; - } - - #endregion - - /// - /// Gets a value indicating whether writing progress is supported. - /// - internal protected override bool SupportsWriteProgress => true; - - /// - /// Requests that the HostUI implementation read a command line - /// from the user to be executed in the integrated console command - /// loop. - /// - /// - /// A CancellationToken used to cancel the command line request. - /// - /// A Task that can be awaited for the resulting input string. - protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return this.consoleReadLine.ReadCommandLineAsync(cancellationToken); - } - - /// - /// Creates an InputPrompt handle to use for displaying input - /// prompts to the user. - /// - /// A new InputPromptHandler instance. - protected override InputPromptHandler OnCreateInputPromptHandler() - { - return new TerminalInputPromptHandler( - this.consoleReadLine, - this, - this.Logger); - } - - /// - /// Creates a ChoicePromptHandler to use for displaying a - /// choice prompt to the user. - /// - /// A new ChoicePromptHandler instance. - protected override ChoicePromptHandler OnCreateChoicePromptHandler() - { - return new TerminalChoicePromptHandler( - this.consoleReadLine, - this, - this.Logger); - } - - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - public override void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor) - { - ConsoleColor oldForegroundColor = System.Console.ForegroundColor; - ConsoleColor oldBackgroundColor = System.Console.BackgroundColor; - - System.Console.ForegroundColor = foregroundColor; - System.Console.BackgroundColor = ((int)backgroundColor != -1) ? backgroundColor : oldBackgroundColor; - - System.Console.Write(outputString + (includeNewLine ? Environment.NewLine : "")); - - System.Console.ForegroundColor = oldForegroundColor; - System.Console.BackgroundColor = oldBackgroundColor; - } - - /// - /// Invoked by to display a progress record. - /// - /// - /// Unique identifier of the source of the record. An int64 is used because typically, - /// the 'this' pointer of the command from whence the record is originating is used, and - /// that may be from a remote Runspace on a 64-bit machine. - /// - /// - /// The record being reported to the host. - /// - protected override void WriteProgressImpl(long sourceId, ProgressRecord record) - { - this.internalHostUI.WriteProgress(sourceId, record); - } - - /// - /// Sends a progress update event to the user. - /// - /// The source ID of the progress event. - /// The details of the activity's current progress. - protected override void UpdateProgress( - long sourceId, - ProgressDetails progressDetails) - { - } - } -} diff --git a/src/PowerShellEditorServices/Session/HostDetails.cs b/src/PowerShellEditorServices/Session/HostDetails.cs deleted file mode 100644 index a20bc8cf3..000000000 --- a/src/PowerShellEditorServices/Session/HostDetails.cs +++ /dev/null @@ -1,92 +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.Session -{ - /// - /// Contains details about the current host application (most - /// likely the editor which is using the host process). - /// - public class HostDetails - { - #region Constants - - /// - /// The default host name for PowerShell Editor Services. Used - /// if no host name is specified by the host application. - /// - public const string DefaultHostName = "PowerShell Editor Services Host"; - - /// - /// The default host ID for PowerShell Editor Services. Used - /// for the host-specific profile path if no host ID is specified. - /// - public const string DefaultHostProfileId = "Microsoft.PowerShellEditorServices"; - - /// - /// The default host version for PowerShell Editor Services. If - /// no version is specified by the host application, we use 0.0.0 - /// to indicate a lack of version. - /// - public static readonly Version DefaultHostVersion = new Version("0.0.0"); - - /// - /// The default host details in a HostDetails object. - /// - public static readonly HostDetails Default = new HostDetails(null, null, null); - - #endregion - - #region Properties - - /// - /// Gets the name of the host. - /// - public string Name { get; private set; } - - /// - /// Gets the profile ID of the host, used to determine the - /// host-specific profile path. - /// - public string ProfileId { get; private set; } - - /// - /// Gets the version of the host. - /// - public Version Version { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the HostDetails class. - /// - /// - /// The display name for the host, typically in the form of - /// "[Application Name] Host". - /// - /// - /// The identifier of the PowerShell host to use for its profile path. - /// loaded. Used to resolve a profile path of the form 'X_profile.ps1' - /// where 'X' represents the value of hostProfileId. If null, a default - /// will be used. - /// - /// The host application's version. - public HostDetails( - string name, - string profileId, - Version version) - { - this.Name = name ?? DefaultHostName; - this.ProfileId = profileId ?? DefaultHostProfileId; - this.Version = version ?? DefaultHostVersion; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/IPromptContext.cs b/src/PowerShellEditorServices/Session/IPromptContext.cs deleted file mode 100644 index 157715e7d..000000000 --- a/src/PowerShellEditorServices/Session/IPromptContext.cs +++ /dev/null @@ -1,67 +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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Provides methods for interacting with implementations of ReadLine. - /// - public interface IPromptContext - { - /// - /// Read a string that has been input by the user. - /// - /// Indicates if ReadLine should act like a command REPL. - /// - /// The cancellation token can be used to cancel reading user input. - /// - /// - /// A task object that represents the completion of reading input. The Result property will - /// return the input string. - /// - Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken); - - /// - /// Performs any additional actions required to cancel the current ReadLine invocation. - /// - void AbortReadLine(); - - /// - /// Creates a task that completes when the current ReadLine invocation has been aborted. - /// - /// - /// A task object that represents the abortion of the current ReadLine invocation. - /// - Task AbortReadLineAsync(); - - /// - /// Blocks until the current ReadLine invocation has exited. - /// - void WaitForReadLineExit(); - - /// - /// Creates a task that completes when the current ReadLine invocation has exited. - /// - /// - /// A task object that represents the exit of the current ReadLine invocation. - /// - Task WaitForReadLineExitAsync(); - - /// - /// Adds the specified command to the history managed by the ReadLine implementation. - /// - /// The command to record. - void AddToHistory(string command); - - /// - /// Forces the prompt handler to trigger PowerShell event handling, reliquishing control - /// of the pipeline thread during event processing. - /// - void ForcePSEventHandling(); - } -} diff --git a/src/PowerShellEditorServices/Session/IRunspaceCapability.cs b/src/PowerShellEditorServices/Session/IRunspaceCapability.cs deleted file mode 100644 index 38d14fb96..000000000 --- a/src/PowerShellEditorServices/Session/IRunspaceCapability.cs +++ /dev/null @@ -1,12 +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.Session -{ - internal interface IRunspaceCapability - { - // NOTE: This interface is intentionally empty for now. - } -} diff --git a/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs b/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs deleted file mode 100644 index 55540ba9d..000000000 --- a/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs +++ /dev/null @@ -1,33 +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.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - internal interface IVersionSpecificOperations - { - void ConfigureDebugger(Runspace runspace); - - void PauseDebugger(Runspace runspace); - - IEnumerable ExecuteCommandInDebugger( - PowerShellContext powerShellContext, - Runspace currentRunspace, - PSCommand psCommand, - bool sendOutputToHost, - out DebuggerResumeAction? debuggerResumeAction); - - void StopCommandInDebugger(PowerShellContext powerShellContext); - - bool IsDebuggerStopped(PromptNest promptNest, Runspace runspace); - - void ExitNestedPrompt(PSHost host); - } -} - diff --git a/src/PowerShellEditorServices/Session/InvocationEventQueue.cs b/src/PowerShellEditorServices/Session/InvocationEventQueue.cs deleted file mode 100644 index b6e9a9c0b..000000000 --- a/src/PowerShellEditorServices/Session/InvocationEventQueue.cs +++ /dev/null @@ -1,263 +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.Management.Automation.Runspaces; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using System.Threading; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - using System.Management.Automation; - - /// - /// Provides the ability to take over the current pipeline in a runspace. - /// - internal class InvocationEventQueue - { - private const string ShouldProcessInExecutionThreadPropertyName = "ShouldProcessInExecutionThread"; - - private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = - typeof(PSEventSubscriber) - .GetProperty( - ShouldProcessInExecutionThreadPropertyName, - BindingFlags.Instance | BindingFlags.NonPublic); - - private readonly PromptNest _promptNest; - - private readonly Runspace _runspace; - - private readonly PowerShellContext _powerShellContext; - - private InvocationRequest _invocationRequest; - - private SemaphoreSlim _lock = AsyncUtils.CreateSimpleLockingSemaphore(); - - private InvocationEventQueue(PowerShellContext powerShellContext, PromptNest promptNest) - { - _promptNest = promptNest; - _powerShellContext = powerShellContext; - _runspace = powerShellContext.CurrentRunspace.Runspace; - } - - internal static InvocationEventQueue Create(PowerShellContext powerShellContext, PromptNest promptNest) - { - var eventQueue = new InvocationEventQueue(powerShellContext, promptNest); - eventQueue.CreateInvocationSubscriber(); - return eventQueue; - } - - /// - /// Executes a command on the main pipeline thread through - /// eventing. A event subscriber will - /// be created that creates a nested PowerShell instance for - /// to utilize. - /// - /// - /// Avoid using this method directly if possible. - /// will route commands - /// through this method if required. - /// - /// The expected result type. - /// The to be executed. - /// - /// Error messages from PowerShell will be written to the . - /// - /// Specifies options to be used when executing this command. - /// - /// An awaitable which will provide results once the command - /// execution completes. - /// - internal async Task> ExecuteCommandOnIdleAsync( - PSCommand psCommand, - StringBuilder errorMessages, - ExecutionOptions executionOptions) - { - var request = new PipelineExecutionRequest( - _powerShellContext, - psCommand, - errorMessages, - executionOptions); - - await SetInvocationRequestAsync( - new InvocationRequest( - pwsh => request.ExecuteAsync().GetAwaiter().GetResult())); - - try - { - return await request.Results; - } - finally - { - await SetInvocationRequestAsync(request: null); - } - } - - /// - /// Marshals a to run on the pipeline thread. A new - /// will be created for the invocation. - /// - /// - /// The to invoke on the pipeline thread. The nested - /// instance for the created - /// will be passed as an argument. - /// - /// - /// An awaitable that the caller can use to know when execution completes. - /// - internal async Task InvokeOnPipelineThreadAsync(Action invocationAction) - { - var request = new InvocationRequest(pwsh => - { - using (_promptNest.GetRunspaceHandle(CancellationToken.None, isReadLine: false)) - { - pwsh.Runspace = _runspace; - invocationAction(pwsh); - } - }); - - await SetInvocationRequestAsync(request); - try - { - await request.Task; - } - finally - { - await SetInvocationRequestAsync(null); - } - } - - private async Task WaitForExistingRequestAsync() - { - InvocationRequest existingRequest; - await _lock.WaitAsync(); - try - { - existingRequest = _invocationRequest; - if (existingRequest == null || existingRequest.Task.IsCompleted) - { - return; - } - } - finally - { - _lock.Release(); - } - - await existingRequest.Task; - } - - private async Task SetInvocationRequestAsync(InvocationRequest request) - { - await WaitForExistingRequestAsync(); - await _lock.WaitAsync(); - try - { - _invocationRequest = request; - } - finally - { - _lock.Release(); - } - - _powerShellContext.ForcePSEventHandling(); - } - - private void OnPowerShellIdle(object sender, EventArgs e) - { - if (!_lock.Wait(0)) - { - return; - } - - InvocationRequest currentRequest = null; - try - { - if (_invocationRequest == null) - { - return; - } - - currentRequest = _invocationRequest; - } - finally - { - _lock.Release(); - } - - _promptNest.PushPromptContext(); - try - { - currentRequest.Invoke(_promptNest.GetPowerShell()); - } - finally - { - _promptNest.PopPromptContext(); - } - } - - private PSEventSubscriber CreateInvocationSubscriber() - { - PSEventSubscriber subscriber = _runspace.Events.SubscribeEvent( - source: null, - eventName: PSEngineEvent.OnIdle, - sourceIdentifier: PSEngineEvent.OnIdle, - data: null, - handlerDelegate: OnPowerShellIdle, - supportEvent: true, - forwardEvent: false); - - SetSubscriberExecutionThreadWithReflection(subscriber); - - subscriber.Unsubscribed += OnInvokerUnsubscribed; - - return subscriber; - } - - private void OnInvokerUnsubscribed(object sender, PSEventUnsubscribedEventArgs e) - { - CreateInvocationSubscriber(); - } - - private void SetSubscriberExecutionThreadWithReflection(PSEventSubscriber subscriber) - { - // We need to create the PowerShell object in the same thread so we can get a nested - // PowerShell. This is the only way to consistently take control of the pipeline. The - // alternative is to make the subscriber a script block and have that create and process - // the PowerShell object, but that puts us in a different SessionState and is a lot slower. - s_shouldProcessInExecutionThreadProperty.SetValue(subscriber, true); - } - - private class InvocationRequest : TaskCompletionSource - { - private readonly Action _invocationAction; - - internal InvocationRequest(Action invocationAction) - { - _invocationAction = invocationAction; - } - - internal void Invoke(PowerShell pwsh) - { - try - { - _invocationAction(pwsh); - - // Ensure the result is set in another thread otherwise the caller - // may take over the pipeline thread. - System.Threading.Tasks.Task.Run(() => SetResult(true)); - } - catch (Exception e) - { - System.Threading.Tasks.Task.Run(() => SetException(e)); - } - } - } - } -} diff --git a/src/PowerShellEditorServices/Session/LegacyReadLineContext.cs b/src/PowerShellEditorServices/Session/LegacyReadLineContext.cs deleted file mode 100644 index ad68d0512..000000000 --- a/src/PowerShellEditorServices/Session/LegacyReadLineContext.cs +++ /dev/null @@ -1,56 +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.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Console; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - internal class LegacyReadLineContext : IPromptContext - { - private readonly ConsoleReadLine _legacyReadLine; - - internal LegacyReadLineContext(PowerShellContext powerShellContext) - { - _legacyReadLine = new ConsoleReadLine(powerShellContext); - } - - public Task AbortReadLineAsync() - { - return Task.FromResult(true); - } - - public async Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - return await _legacyReadLine.InvokeLegacyReadLineAsync(isCommandLine, cancellationToken); - } - - public Task WaitForReadLineExitAsync() - { - return Task.FromResult(true); - } - - public void AddToHistory(string command) - { - // Do nothing, history is managed completely by the PowerShell engine in legacy ReadLine. - } - - public void AbortReadLine() - { - // Do nothing, no additional actions are needed to cancel ReadLine. - } - - public void WaitForReadLineExit() - { - // Do nothing, ReadLine cancellation is instant or not appliciable. - } - - public void ForcePSEventHandling() - { - // Do nothing, the pipeline thread is not occupied by legacy ReadLine. - } - } -} diff --git a/src/PowerShellEditorServices/Session/OutputType.cs b/src/PowerShellEditorServices/Session/OutputType.cs deleted file mode 100644 index ad67f6891..000000000 --- a/src/PowerShellEditorServices/Session/OutputType.cs +++ /dev/null @@ -1,41 +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 -{ - /// - /// Enumerates the types of output lines that will be sent - /// to an IConsoleHost implementation. - /// - public enum OutputType - { - /// - /// A normal output line, usually written with the or Write-Host or - /// Write-Output cmdlets. - /// - Normal, - - /// - /// A debug output line, written with the Write-Debug cmdlet. - /// - Debug, - - /// - /// A verbose output line, written with the Write-Verbose cmdlet. - /// - Verbose, - - /// - /// A warning output line, written with the Write-Warning cmdlet. - /// - Warning, - - /// - /// An error output line, written with the Write-Error cmdlet or - /// as a result of some error during PowerShell pipeline execution. - /// - Error - } -} diff --git a/src/PowerShellEditorServices/Session/OutputWrittenEventArgs.cs b/src/PowerShellEditorServices/Session/OutputWrittenEventArgs.cs deleted file mode 100644 index b1408a991..000000000 --- a/src/PowerShellEditorServices/Session/OutputWrittenEventArgs.cs +++ /dev/null @@ -1,65 +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 -{ - /// - /// Provides details about output that has been written to the - /// PowerShell host. - /// - public class OutputWrittenEventArgs - { - /// - /// Gets the text of the output. - /// - public string OutputText { get; private set; } - - /// - /// Gets the type of the output. - /// - public OutputType OutputType { get; private set; } - - /// - /// Gets a boolean which indicates whether a newline - /// should be written after the output. - /// - public bool IncludeNewLine { get; private set; } - - /// - /// Gets the foreground color of the output text. - /// - public ConsoleColor ForegroundColor { get; private set; } - - /// - /// Gets the background color of the output text. - /// - public ConsoleColor BackgroundColor { get; private set; } - - /// - /// Creates an instance of the OutputWrittenEventArgs class. - /// - /// The text of the output. - /// A boolean which indicates whether a newline should be written after the output. - /// The type of the output. - /// The foreground color of the output text. - /// The background color of the output text. - public OutputWrittenEventArgs( - string outputText, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor) - { - this.OutputText = outputText; - this.IncludeNewLine = includeNewLine; - this.OutputType = outputType; - this.ForegroundColor = foregroundColor; - this.BackgroundColor = backgroundColor; - } - } -} - diff --git a/src/PowerShellEditorServices/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Session/PSReadLinePromptContext.cs deleted file mode 100644 index a71e32ed9..000000000 --- a/src/PowerShellEditorServices/Session/PSReadLinePromptContext.cs +++ /dev/null @@ -1,203 +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.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System; -using System.Management.Automation.Runspaces; -using Microsoft.PowerShell.EditorServices.Console; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session { - using System.Management.Automation; - - internal class PSReadLinePromptContext : IPromptContext { - private const string ReadLineScript = @" - [System.Diagnostics.DebuggerHidden()] - [System.Diagnostics.DebuggerStepThrough()] - param() - return [Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null]::ReadLine( - $Host.Runspace, - $ExecutionContext, - $args[0])"; - - private const string ReadLineInitScript = @" - [System.Diagnostics.DebuggerHidden()] - [System.Diagnostics.DebuggerStepThrough()] - param() - end { - $module = Get-Module -ListAvailable PSReadLine | - Where-Object Version -eq '2.0.0' | - Where-Object { $_.PrivateData.PSData.Prerelease -notin 'beta1','beta2','beta3' } | - Sort-Object -Descending Version | - Select-Object -First 1 - if (-not $module) { - return - } - - Import-Module -ModuleInfo $module - return [Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null] - }"; - - private readonly PowerShellContext _powerShellContext; - - private PromptNest _promptNest; - - private InvocationEventQueue _invocationEventQueue; - - private ConsoleReadLine _consoleReadLine; - - private CancellationTokenSource _readLineCancellationSource; - - private PSReadLineProxy _readLineProxy; - - internal PSReadLinePromptContext( - PowerShellContext powerShellContext, - PromptNest promptNest, - InvocationEventQueue invocationEventQueue, - PSReadLineProxy readLineProxy) - { - _promptNest = promptNest; - _powerShellContext = powerShellContext; - _invocationEventQueue = invocationEventQueue; - _consoleReadLine = new ConsoleReadLine(powerShellContext); - _readLineProxy = readLineProxy; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return; - } - - _readLineProxy.OverrideReadKey( - intercept => ConsoleProxy.UnixReadKey( - intercept, - _readLineCancellationSource.Token)); - } - - internal static bool TryGetPSReadLineProxy( - ILogger logger, - Runspace runspace, - out PSReadLineProxy readLineProxy) - { - readLineProxy = null; - using (var pwsh = PowerShell.Create()) - { - pwsh.Runspace = runspace; - var psReadLineType = pwsh - .AddScript(ReadLineInitScript) - .Invoke() - .FirstOrDefault(); - - if (psReadLineType == null) - { - return false; - } - - try - { - readLineProxy = new PSReadLineProxy(psReadLineType, logger); - } - catch (InvalidOperationException) - { - // The Type we got back from PowerShell doesn't have the members we expected. - // Could be an older version, a custom build, or something a newer version with - // breaking changes. - return false; - } - } - - return true; - } - - public async Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - _readLineCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var localTokenSource = _readLineCancellationSource; - if (localTokenSource.Token.IsCancellationRequested) - { - throw new TaskCanceledException(); - } - - try - { - if (!isCommandLine) - { - return await _consoleReadLine.InvokeLegacyReadLineAsync( - false, - _readLineCancellationSource.Token); - } - - var result = (await _powerShellContext.ExecuteCommandAsync( - new PSCommand() - .AddScript(ReadLineScript) - .AddArgument(_readLineCancellationSource.Token), - null, - new ExecutionOptions() - { - WriteErrorsToHost = false, - WriteOutputToHost = false, - InterruptCommandPrompt = false, - AddToHistory = false, - IsReadLine = isCommandLine - })) - .FirstOrDefault(); - - return cancellationToken.IsCancellationRequested - ? string.Empty - : result; - } - finally - { - _readLineCancellationSource = null; - } - } - - public void AbortReadLine() - { - if (_readLineCancellationSource == null) - { - return; - } - - _readLineCancellationSource.Cancel(); - - WaitForReadLineExit(); - } - - public async Task AbortReadLineAsync() { - if (_readLineCancellationSource == null) - { - return; - } - - _readLineCancellationSource.Cancel(); - - await WaitForReadLineExitAsync(); - } - - public void WaitForReadLineExit() - { - using (_promptNest.GetRunspaceHandle(CancellationToken.None, isReadLine: true)) - { } - } - - public async Task WaitForReadLineExitAsync() { - using (await _promptNest.GetRunspaceHandleAsync(CancellationToken.None, isReadLine: true)) - { } - } - - public void AddToHistory(string command) - { - _readLineProxy.AddToHistory(command); - } - - public void ForcePSEventHandling() - { - _readLineProxy.ForcePSEventHandling(); - } - } -} diff --git a/src/PowerShellEditorServices/Session/PSReadLineProxy.cs b/src/PowerShellEditorServices/Session/PSReadLineProxy.cs deleted file mode 100644 index 50aaf4af3..000000000 --- a/src/PowerShellEditorServices/Session/PSReadLineProxy.cs +++ /dev/null @@ -1,119 +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.Reflection; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - internal class PSReadLineProxy - { - private const string FieldMemberType = "field"; - - private const string MethodMemberType = "method"; - - private const string AddToHistoryMethodName = "AddToHistory"; - - private const string SetKeyHandlerMethodName = "SetKeyHandler"; - - private const string ReadKeyOverrideFieldName = "_readKeyOverride"; - - private const string VirtualTerminalTypeName = "Microsoft.PowerShell.Internal.VirtualTerminal"; - - private const string ForcePSEventHandlingMethodName = "ForcePSEventHandling"; - - private static readonly Type[] s_setKeyHandlerTypes = - { - typeof(string[]), - typeof(Action), - typeof(string), - typeof(string) - }; - - private static readonly Type[] s_addToHistoryTypes = { typeof(string) }; - - private readonly FieldInfo _readKeyOverrideField; - - internal PSReadLineProxy(Type psConsoleReadLine, ILogger logger) - { - ForcePSEventHandling = - (Action)psConsoleReadLine.GetMethod( - ForcePSEventHandlingMethodName, - BindingFlags.Static | BindingFlags.NonPublic) - ?.CreateDelegate(typeof(Action)); - - AddToHistory = (Action)psConsoleReadLine.GetMethod( - AddToHistoryMethodName, - s_addToHistoryTypes) - ?.CreateDelegate(typeof(Action)); - - SetKeyHandler = - (Action, string, string>)psConsoleReadLine.GetMethod( - SetKeyHandlerMethodName, - s_setKeyHandlerTypes) - ?.CreateDelegate(typeof(Action, string, string>)); - - _readKeyOverrideField = psConsoleReadLine.GetTypeInfo().Assembly - .GetType(VirtualTerminalTypeName) - ?.GetField(ReadKeyOverrideFieldName, BindingFlags.Static | BindingFlags.NonPublic); - - if (_readKeyOverrideField == null) - { - throw NewInvalidPSReadLineVersionException( - FieldMemberType, - ReadKeyOverrideFieldName, - logger); - } - - if (SetKeyHandler == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - SetKeyHandlerMethodName, - logger); - } - - if (AddToHistory == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - AddToHistoryMethodName, - logger); - } - - if (ForcePSEventHandling == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - ForcePSEventHandlingMethodName, - logger); - } - } - - internal Action AddToHistory { get; } - - internal Action, object>, string, string> SetKeyHandler { get; } - - internal Action ForcePSEventHandling { get; } - - internal void OverrideReadKey(Func readKeyFunc) - { - _readKeyOverrideField.SetValue(null, readKeyFunc); - } - - private static InvalidOperationException NewInvalidPSReadLineVersionException( - string memberType, - string memberName, - ILogger logger) - { - logger.Write( - LogLevel.Error, - $"The loaded version of PSReadLine is not supported. The {memberType} \"{memberName}\" was not found."); - - return new InvalidOperationException(); - } - } -} diff --git a/src/PowerShellEditorServices/Session/PipelineExecutionRequest.cs b/src/PowerShellEditorServices/Session/PipelineExecutionRequest.cs deleted file mode 100644 index 1c69f6a15..000000000 --- a/src/PowerShellEditorServices/Session/PipelineExecutionRequest.cs +++ /dev/null @@ -1,80 +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.Collections.Generic; -using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - internal interface IPipelineExecutionRequest - { - Task ExecuteAsync(); - - Task WaitTask { get; } - } - - /// - /// Contains details relating to a request to execute a - /// command on the PowerShell pipeline thread. - /// - /// The expected result type of the execution. - internal class PipelineExecutionRequest : IPipelineExecutionRequest - { - private PowerShellContext _powerShellContext; - private PSCommand _psCommand; - private StringBuilder _errorMessages; - private ExecutionOptions _executionOptions; - private TaskCompletionSource> _resultsTask; - - public Task> Results - { - get { return this._resultsTask.Task; } - } - - public Task WaitTask { get { return Results; } } - - public PipelineExecutionRequest( - PowerShellContext powerShellContext, - PSCommand psCommand, - StringBuilder errorMessages, - bool sendOutputToHost) - : this( - powerShellContext, - psCommand, - errorMessages, - new ExecutionOptions() - { - WriteOutputToHost = sendOutputToHost - }) - { } - - - public PipelineExecutionRequest( - PowerShellContext powerShellContext, - PSCommand psCommand, - StringBuilder errorMessages, - ExecutionOptions executionOptions) - { - _powerShellContext = powerShellContext; - _psCommand = psCommand; - _errorMessages = errorMessages; - _executionOptions = executionOptions; - _resultsTask = new TaskCompletionSource>(); - } - - public async Task ExecuteAsync() - { - var results = - await _powerShellContext.ExecuteCommandAsync( - _psCommand, - _errorMessages, - _executionOptions); - - var unusedTask = Task.Run(() => _resultsTask.SetResult(results)); - } - } -} diff --git a/src/PowerShellEditorServices/Session/PowerShell5Operations.cs b/src/PowerShellEditorServices/Session/PowerShell5Operations.cs deleted file mode 100644 index 20954ac21..000000000 --- a/src/PowerShellEditorServices/Session/PowerShell5Operations.cs +++ /dev/null @@ -1,107 +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.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - internal class PowerShell5Operations : IVersionSpecificOperations - { - public void ConfigureDebugger(Runspace runspace) - { - if (runspace.Debugger != null) - { - runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); - } - } - - public virtual void PauseDebugger(Runspace runspace) - { - if (runspace.Debugger != null) - { - runspace.Debugger.SetDebuggerStepMode(true); - } - } - - public virtual bool IsDebuggerStopped(PromptNest promptNest, Runspace runspace) - { - return runspace.Debugger.InBreakpoint || (promptNest.IsRemote && promptNest.IsInDebugger); - } - - public IEnumerable ExecuteCommandInDebugger( - PowerShellContext powerShellContext, - Runspace currentRunspace, - PSCommand psCommand, - bool sendOutputToHost, - out DebuggerResumeAction? debuggerResumeAction) - { - debuggerResumeAction = null; - PSDataCollection outputCollection = new PSDataCollection(); - - if (sendOutputToHost) - { - outputCollection.DataAdded += - (obj, e) => - { - for (int i = e.Index; i < outputCollection.Count; i++) - { - powerShellContext.WriteOutput( - outputCollection[i].ToString(), - true); - } - }; - } - - DebuggerCommandResults commandResults = - currentRunspace.Debugger.ProcessCommand( - psCommand, - outputCollection); - - // Pass along the debugger's resume action if the user's - // command caused one to be returned - debuggerResumeAction = commandResults.ResumeAction; - - IEnumerable results = null; - if (typeof(TResult) != typeof(PSObject)) - { - results = - outputCollection - .Select(pso => pso.BaseObject) - .Cast(); - } - else - { - results = outputCollection.Cast(); - } - - return results; - } - - public void StopCommandInDebugger(PowerShellContext powerShellContext) - { - // If the RunspaceAvailability is None, the runspace is dead and we should not try to run anything in it. - if (powerShellContext.CurrentRunspace.Runspace.RunspaceAvailability != RunspaceAvailability.None) - { - powerShellContext.CurrentRunspace.Runspace.Debugger.StopProcessCommand(); - } - } - - public void ExitNestedPrompt(PSHost host) - { - try - { - host.ExitNestedPrompt(); - } - catch (FlowControlException) - { - } - } - } -} - diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Session/PowerShellContext.cs deleted file mode 100644 index fbfe46cb2..000000000 --- a/src/PowerShellEditorServices/Session/PowerShellContext.cs +++ /dev/null @@ -1,2532 +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.Collections.ObjectModel; -using System.Globalization; -using System.IO; -using System.Runtime.InteropServices; -using System.Linq; -using System.Management.Automation.Host; -using System.Management.Automation.Remoting; -using System.Management.Automation.Runspaces; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Session; -using Microsoft.PowerShell.EditorServices.Session.Capabilities; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - using System.ComponentModel; - using System.Management.Automation; - - /// - /// Manages the lifetime and usage of a PowerShell session. - /// Handles nested PowerShell prompts and also manages execution of - /// commands whether inside or outside of the debugger. - /// - public class PowerShellContext : IDisposable, IHostSupportsInteractiveSession - { - private static readonly Action s_runspaceApartmentStateSetter; - - static PowerShellContext() - { - // PowerShell ApartmentState APIs aren't available in PSStandard, so we need to use reflection - if (!Utils.IsNetCore) - { - MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); - Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); - s_runspaceApartmentStateSetter = (Action)setter; - } - } - - #region Fields - - private readonly SemaphoreSlim resumeRequestHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - private bool isPSReadLineEnabled; - private ILogger logger; - private PowerShell powerShell; - private bool ownsInitialRunspace; - private RunspaceDetails initialRunspace; - private SessionDetails mostRecentSessionDetails; - - private ProfilePaths profilePaths; - - private IVersionSpecificOperations versionSpecificOperations; - - private Stack runspaceStack = new Stack(); - - private int isCommandLoopRestarterSet; - - #endregion - - #region Properties - - private IPromptContext PromptContext { get; set; } - - private PromptNest PromptNest { get; set; } - - private InvocationEventQueue InvocationEventQueue { get; set; } - - private EngineIntrinsics EngineIntrinsics { get; set; } - - private PSHost ExternalHost { get; set; } - - /// - /// Gets a boolean that indicates whether the debugger is currently stopped, - /// either at a breakpoint or because the user broke execution. - /// - public bool IsDebuggerStopped => - this.versionSpecificOperations.IsDebuggerStopped( - PromptNest, - CurrentRunspace.Runspace); - - /// - /// Gets the current state of the session. - /// - public PowerShellContextState SessionState - { - get; - private set; - } - - /// - /// Gets the PowerShell version details for the initial local runspace. - /// - public PowerShellVersionDetails LocalPowerShellVersion - { - get; - private set; - } - - /// - /// Gets or sets an IHostOutput implementation for use in - /// writing output to the console. - /// - private IHostOutput ConsoleWriter { get; set; } - - private IHostInput ConsoleReader { get; set; } - - /// - /// Gets details pertaining to the current runspace. - /// - public RunspaceDetails CurrentRunspace - { - get; - private set; - } - - /// - /// Gets a value indicating whether the current runspace - /// is ready for a command - /// - public bool IsAvailable => this.SessionState == PowerShellContextState.Ready; - - /// - /// Gets the working directory path the PowerShell context was inititially set when the debugger launches. - /// This path is used to determine whether a script in the call stack is an "external" script. - /// - public string InitialWorkingDirectory { get; private set; } - - #endregion - - #region Constructors - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - /// - /// Indicates whether PSReadLine should be used if possible - /// - public PowerShellContext(ILogger logger, bool isPSReadLineEnabled) - { - this.logger = logger; - this.isPSReadLineEnabled = isPSReadLineEnabled; - } - - /// - /// - /// - /// - /// - /// - /// The EditorServicesPSHostUserInterface to use for this instance. - /// - /// An ILogger implementation to use for this instance. - /// - public static Runspace CreateRunspace( - HostDetails hostDetails, - PowerShellContext powerShellContext, - EditorServicesPSHostUserInterface hostUserInterface, - ILogger logger) - { - var psHost = new EditorServicesPSHost(powerShellContext, hostDetails, hostUserInterface, logger); - powerShellContext.ConsoleWriter = hostUserInterface; - powerShellContext.ConsoleReader = hostUserInterface; - return CreateRunspace(psHost); - } - - /// - /// - /// - /// - /// - public static Runspace CreateRunspace(PSHost psHost) - { - InitialSessionState initialSessionState; - if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") { - initialSessionState = InitialSessionState.CreateDefault(); - } else { - initialSessionState = InitialSessionState.CreateDefault2(); - } - - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState); - - // Windows PowerShell must be hosted in STA mode - // This must be set on the runspace *before* it is opened - if (s_runspaceApartmentStateSetter != null) - { - s_runspaceApartmentStateSetter(runspace, ApartmentState.STA); - } - - runspace.ThreadOptions = PSThreadOptions.ReuseThread; - runspace.Open(); - - return runspace; - } - - /// - /// Initializes a new instance of the PowerShellContext class using - /// an existing runspace for the session. - /// - /// An object containing the profile paths for the session. - /// The initial runspace to use for this instance. - /// If true, the PowerShellContext owns this runspace. - public void Initialize( - ProfilePaths profilePaths, - Runspace initialRunspace, - bool ownsInitialRunspace) - { - this.Initialize(profilePaths, initialRunspace, ownsInitialRunspace, null); - } - - /// - /// Initializes a new instance of the PowerShellContext class using - /// an existing runspace for the session. - /// - /// An object containing the profile paths for the session. - /// The initial runspace to use for this instance. - /// If true, the PowerShellContext owns this runspace. - /// An IHostOutput implementation. Optional. - public void Initialize( - ProfilePaths profilePaths, - Runspace initialRunspace, - bool ownsInitialRunspace, - IHostOutput consoleHost) - { - Validate.IsNotNull("initialRunspace", initialRunspace); - - this.ownsInitialRunspace = ownsInitialRunspace; - this.SessionState = PowerShellContextState.NotStarted; - this.ConsoleWriter = consoleHost; - this.ConsoleReader = consoleHost as IHostInput; - - // Get the PowerShell runtime version - this.LocalPowerShellVersion = - PowerShellVersionDetails.GetVersionDetails( - initialRunspace, - this.logger); - - this.powerShell = PowerShell.Create(); - this.powerShell.Runspace = initialRunspace; - - this.initialRunspace = - new RunspaceDetails( - initialRunspace, - this.GetSessionDetailsInRunspace(initialRunspace), - this.LocalPowerShellVersion, - RunspaceLocation.Local, - RunspaceContext.Original, - null); - this.CurrentRunspace = this.initialRunspace; - - // Write out the PowerShell version for tracking purposes - this.logger.Write( - LogLevel.Normal, - string.Format( - "PowerShell runtime version: {0}, edition: {1}", - this.LocalPowerShellVersion.Version, - this.LocalPowerShellVersion.Edition)); - - Version powerShellVersion = this.LocalPowerShellVersion.Version; - if (powerShellVersion >= new Version(5, 0)) - { - this.versionSpecificOperations = new PowerShell5Operations(); - } - else - { - throw new NotSupportedException( - "This computer has an unsupported version of PowerShell installed: " + - powerShellVersion.ToString()); - } - - if (this.LocalPowerShellVersion.Edition != "Linux") - { - // TODO: Should this be configurable? - this.SetExecutionPolicy(ExecutionPolicy.RemoteSigned); - } - - // Set up the runspace - this.ConfigureRunspace(this.CurrentRunspace); - - // Add runspace capabilities - this.ConfigureRunspaceCapabilities(this.CurrentRunspace); - - // Set the $profile variable in the runspace - this.profilePaths = profilePaths; - if (this.profilePaths != null) - { - this.SetProfileVariableInCurrentRunspace(profilePaths); - } - - // Now that initialization is complete we can watch for InvocationStateChanged - this.SessionState = PowerShellContextState.Ready; - - // EngineIntrinsics is used in some instances to interact with the initial - // runspace without having to wait for PSReadLine to check for events. - this.EngineIntrinsics = - initialRunspace - .SessionStateProxy - .PSVariable - .GetValue("ExecutionContext") - as EngineIntrinsics; - - // The external host is used to properly exit from a nested prompt that - // was entered by the user. - this.ExternalHost = - initialRunspace - .SessionStateProxy - .PSVariable - .GetValue("Host") - as PSHost; - - // Now that the runspace is ready, enqueue it for first use - this.PromptNest = new PromptNest( - this, - this.powerShell, - this.ConsoleReader, - this.versionSpecificOperations); - this.InvocationEventQueue = InvocationEventQueue.Create(this, this.PromptNest); - - if (powerShellVersion.Major >= 5 && - this.isPSReadLineEnabled && - PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, out PSReadLineProxy proxy)) - { - this.PromptContext = new PSReadLinePromptContext( - this, - this.PromptNest, - this.InvocationEventQueue, - proxy); - } - else - { - this.PromptContext = new LegacyReadLineContext(this); - } - } - - /// - /// Imports the PowerShellEditorServices.Commands module into - /// the runspace. This method will be moved somewhere else soon. - /// - /// - /// - public Task ImportCommandsModuleAsync(string moduleBasePath) - { - PSCommand importCommand = new PSCommand(); - importCommand - .AddCommand("Import-Module") - .AddArgument( - Path.Combine( - moduleBasePath, - "PowerShellEditorServices.Commands.psd1")); - - return this.ExecuteCommandAsync(importCommand, false, false); - } - - private static bool CheckIfRunspaceNeedsEventHandlers(RunspaceDetails runspaceDetails) - { - // The only types of runspaces that need to be configured are: - // - Locally created runspaces - // - Local process entered with Enter-PSHostProcess - // - Remote session entered with Enter-PSSession - return - (runspaceDetails.Location == RunspaceLocation.Local && - (runspaceDetails.Context == RunspaceContext.Original || - runspaceDetails.Context == RunspaceContext.EnteredProcess)) || - (runspaceDetails.Location == RunspaceLocation.Remote && runspaceDetails.Context == RunspaceContext.Original); - } - - private void ConfigureRunspace(RunspaceDetails runspaceDetails) - { - runspaceDetails.Runspace.StateChanged += this.HandleRunspaceStateChanged; - if (runspaceDetails.Runspace.Debugger != null) - { - runspaceDetails.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - runspaceDetails.Runspace.Debugger.DebuggerStop += OnDebuggerStop; - } - - this.versionSpecificOperations.ConfigureDebugger(runspaceDetails.Runspace); - } - - private void CleanupRunspace(RunspaceDetails runspaceDetails) - { - runspaceDetails.Runspace.StateChanged -= this.HandleRunspaceStateChanged; - if (runspaceDetails.Runspace.Debugger != null) - { - runspaceDetails.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - runspaceDetails.Runspace.Debugger.DebuggerStop -= OnDebuggerStop; - } - } - - #endregion - - #region Public Methods - - /// - /// Gets a RunspaceHandle for the session's runspace. This - /// handle is used to gain temporary ownership of the runspace - /// so that commands can be executed against it directly. - /// - /// A RunspaceHandle instance that gives access to the session's runspace. - public Task GetRunspaceHandleAsync() - { - return this.GetRunspaceHandleImplAsync(CancellationToken.None, isReadLine: false); - } - - /// - /// Gets a RunspaceHandle for the session's runspace. This - /// handle is used to gain temporary ownership of the runspace - /// so that commands can be executed against it directly. - /// - /// A CancellationToken that can be used to cancel the request. - /// A RunspaceHandle instance that gives access to the session's runspace. - public Task GetRunspaceHandleAsync(CancellationToken cancellationToken) - { - return this.GetRunspaceHandleImplAsync(cancellationToken, isReadLine: false); - } - - /// - /// Executes a PSCommand against the session's runspace and returns - /// a collection of results of the expected type. - /// - /// The expected result type. - /// The PSCommand to be executed. - /// - /// If true, causes any output written during command execution to be written to the host. - /// - /// - /// If true, causes any errors encountered during command execution to be written to the host. - /// - /// - /// An awaitable Task which will provide results once the command - /// execution completes. - /// - public async Task> ExecuteCommandAsync( - PSCommand psCommand, - bool sendOutputToHost = false, - bool sendErrorToHost = true) - { - return await ExecuteCommandAsync(psCommand, null, sendOutputToHost, sendErrorToHost); - } - - /// - /// Executes a PSCommand against the session's runspace and returns - /// a collection of results of the expected type. - /// - /// The expected result type. - /// The PSCommand to be executed. - /// Error messages from PowerShell will be written to the StringBuilder. - /// - /// If true, causes any output written during command execution to be written to the host. - /// - /// - /// If true, causes any errors encountered during command execution to be written to the host. - /// - /// - /// If true, adds the command to the user's command history. - /// - /// - /// An awaitable Task which will provide results once the command - /// execution completes. - /// - public Task> ExecuteCommandAsync( - PSCommand psCommand, - StringBuilder errorMessages, - bool sendOutputToHost = false, - bool sendErrorToHost = true, - bool addToHistory = false) - { - return - this.ExecuteCommandAsync( - psCommand, - errorMessages, - new ExecutionOptions - { - WriteOutputToHost = sendOutputToHost, - WriteErrorsToHost = sendErrorToHost, - AddToHistory = addToHistory - }); - } - - /// - /// Executes a PSCommand against the session's runspace and returns - /// a collection of results of the expected type. - /// - /// The expected result type. - /// The PSCommand to be executed. - /// Error messages from PowerShell will be written to the StringBuilder. - /// Specifies options to be used when executing this command. - /// - /// An awaitable Task which will provide results once the command - /// execution completes. - /// - public async Task> ExecuteCommandAsync( - PSCommand psCommand, - StringBuilder errorMessages, - ExecutionOptions executionOptions) - { - // Add history to PSReadLine before cancelling, otherwise it will be restored as the - // cancelled prompt when it's called again. - if (executionOptions.AddToHistory) - { - this.PromptContext.AddToHistory(psCommand.Commands[0].CommandText); - } - - bool hadErrors = false; - RunspaceHandle runspaceHandle = null; - ExecutionTarget executionTarget = ExecutionTarget.PowerShell; - IEnumerable executionResult = Enumerable.Empty(); - var shouldCancelReadLine = - executionOptions.InterruptCommandPrompt || - executionOptions.WriteOutputToHost; - - // If the debugger is active and the caller isn't on the pipeline - // thread, send the command over to that thread to be executed. - // Determine if execution should take place in a different thread - // using the following criteria: - // 1. The current frame in the prompt nest has a thread controller - // (meaning it is a nested prompt or is in the debugger) - // 2. We aren't already on the thread in question - // 3. The command is not a candidate for background invocation - // via PowerShell eventing - // 4. The command cannot be for a PSReadLine pipeline while we - // are currently in a out of process runspace - var threadController = PromptNest.GetThreadController(); - if (!(threadController == null || - !threadController.IsPipelineThread || - threadController.IsCurrentThread() || - this.ShouldExecuteWithEventing(executionOptions) || - (PromptNest.IsRemote && executionOptions.IsReadLine))) - { - this.logger.Write(LogLevel.Verbose, "Passing command execution to pipeline thread."); - - if (shouldCancelReadLine && PromptNest.IsReadLineBusy()) - { - // If a ReadLine pipeline is running in the debugger then we'll hang here - // if we don't cancel it. Typically we can rely on OnExecutionStatusChanged but - // the pipeline request won't even start without clearing the current task. - this.ConsoleReader?.StopCommandLoop(); - } - - // Send the pipeline execution request to the pipeline thread - return await threadController.RequestPipelineExecutionAsync( - new PipelineExecutionRequest( - this, - psCommand, - errorMessages, - executionOptions)); - } - else - { - try - { - // Instruct PowerShell to send output and errors to the host - if (executionOptions.WriteOutputToHost) - { - psCommand.Commands[0].MergeMyResults( - PipelineResultTypes.Error, - PipelineResultTypes.Output); - - psCommand.Commands.Add( - this.GetOutputCommand( - endOfStatement: false)); - } - - executionTarget = GetExecutionTarget(executionOptions); - - // If a ReadLine pipeline is running we can still execute commands that - // don't write output (e.g. command completion) - if (executionTarget == ExecutionTarget.InvocationEvent) - { - return (await this.InvocationEventQueue.ExecuteCommandOnIdleAsync( - psCommand, - errorMessages, - executionOptions)); - } - - // Prompt is stopped and started based on the execution status, so naturally - // we don't want PSReadLine pipelines to factor in. - if (!executionOptions.IsReadLine) - { - this.OnExecutionStatusChanged( - ExecutionStatus.Running, - executionOptions, - false); - } - - runspaceHandle = await this.GetRunspaceHandleAsync(executionOptions.IsReadLine); - if (executionOptions.WriteInputToHost) - { - this.WriteOutput(psCommand.Commands[0].CommandText, true); - } - - if (executionTarget == ExecutionTarget.Debugger) - { - // Manually change the session state for debugger commands because - // we don't have an invocation state event to attach to. - if (!executionOptions.IsReadLine) - { - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Running, - PowerShellExecutionResult.NotFinished, - null)); - } - try - { - return this.ExecuteCommandInDebugger( - psCommand, - executionOptions.WriteOutputToHost); - } - catch (Exception e) - { - logger.Write( - LogLevel.Error, - "Exception occurred while executing debugger command:\r\n\r\n" + e.ToString()); - } - finally - { - if (!executionOptions.IsReadLine) - { - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped, - null)); - } - } - } - - var invocationSettings = new PSInvocationSettings() - { - AddToHistory = executionOptions.AddToHistory - }; - - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Attempting to execute command(s):\r\n\r\n{0}", - GetStringForPSCommand(psCommand))); - - - PowerShell shell = this.PromptNest.GetPowerShell(executionOptions.IsReadLine); - shell.Commands = psCommand; - - // Don't change our SessionState for ReadLine. - if (!executionOptions.IsReadLine) - { - shell.InvocationStateChanged += powerShell_InvocationStateChanged; - } - - shell.Runspace = executionOptions.ShouldExecuteInOriginalRunspace - ? this.initialRunspace.Runspace - : this.CurrentRunspace.Runspace; - try - { - // Nested PowerShell instances can't be invoked asynchronously. This occurs - // in nested prompts and pipeline requests from eventing. - if (shell.IsNested) - { - return shell.Invoke(null, invocationSettings); - } - - return await Task.Factory.StartNew>( - () => shell.Invoke(null, invocationSettings), - CancellationToken.None, // Might need a cancellation token - TaskCreationOptions.None, - TaskScheduler.Default); - } - finally - { - if (!executionOptions.IsReadLine) - { - shell.InvocationStateChanged -= powerShell_InvocationStateChanged; - } - - if (shell.HadErrors) - { - var strBld = new StringBuilder(1024); - strBld.AppendFormat("Execution of the following command(s) completed with errors:\r\n\r\n{0}\r\n", - GetStringForPSCommand(psCommand)); - - int i = 1; - foreach (var error in shell.Streams.Error) - { - if (i > 1) strBld.Append("\r\n\r\n"); - strBld.Append($"Error #{i++}:\r\n"); - strBld.Append(error.ToString() + "\r\n"); - strBld.Append("ScriptStackTrace:\r\n"); - strBld.Append((error.ScriptStackTrace ?? "") + "\r\n"); - strBld.Append($"Exception:\r\n {error.Exception?.ToString() ?? ""}"); - Exception innerEx = error.Exception?.InnerException; - while (innerEx != null) - { - strBld.Append($"InnerException:\r\n {innerEx.ToString()}"); - innerEx = innerEx.InnerException; - } - } - - // We've reported these errors, clear them so they don't keep showing up. - shell.Streams.Error.Clear(); - - var errorMessage = strBld.ToString(); - - errorMessages?.Append(errorMessage); - this.logger.Write(LogLevel.Error, errorMessage); - - hadErrors = true; - } - else - { - this.logger.Write( - LogLevel.Verbose, - "Execution completed successfully."); - } - } - } - catch (PSRemotingDataStructureException e) - { - this.logger.Write( - LogLevel.Error, - "Pipeline stopped while executing command:\r\n\r\n" + e.ToString()); - - errorMessages?.Append(e.Message); - } - catch (PipelineStoppedException e) - { - this.logger.Write( - LogLevel.Error, - "Pipeline stopped while executing command:\r\n\r\n" + e.ToString()); - - errorMessages?.Append(e.Message); - } - catch (RuntimeException e) - { - this.logger.Write( - LogLevel.Warning, - "Runtime exception occurred while executing command:\r\n\r\n" + e.ToString()); - - hadErrors = true; - errorMessages?.Append(e.Message); - - if (executionOptions.WriteErrorsToHost) - { - // Write the error to the host - this.WriteExceptionToHost(e); - } - } - catch (Exception e) - { - this.OnExecutionStatusChanged( - ExecutionStatus.Failed, - executionOptions, - true); - - throw; - } - finally - { - // If the RunspaceAvailability is None, it means that the runspace we're in is dead. - // If this is the case, we should abort the execution which will clean up the runspace - // (and clean up the debugger) and then pop it off the stack. - // An example of when this happens is when the "attach" debug config is used and the - // process you're attached to dies randomly. - if (this.CurrentRunspace.Runspace.RunspaceAvailability == RunspaceAvailability.None) - { - this.AbortExecution(shouldAbortDebugSession: true); - this.PopRunspace(); - } - - // Get the new prompt before releasing the runspace handle - if (executionOptions.WriteOutputToHost) - { - SessionDetails sessionDetails = null; - - // Get the SessionDetails and then write the prompt - if (executionTarget == ExecutionTarget.Debugger) - { - sessionDetails = this.GetSessionDetailsInDebugger(); - } - else if (this.CurrentRunspace.Runspace.RunspaceAvailability == RunspaceAvailability.Available) - { - // This state can happen if the user types a command that causes the - // debugger to exit before we reach this point. No RunspaceHandle - // will exist already so we need to create one and then use it - if (runspaceHandle == null) - { - runspaceHandle = await this.GetRunspaceHandleAsync(); - } - - sessionDetails = this.GetSessionDetailsInRunspace(runspaceHandle.Runspace); - } - else - { - sessionDetails = this.GetSessionDetailsInNestedPipeline(); - } - - // Check if the runspace has changed - this.UpdateRunspaceDetailsIfSessionChanged(sessionDetails); - } - - // Dispose of the execution context - if (runspaceHandle != null) - { - runspaceHandle.Dispose(); - } - - this.OnExecutionStatusChanged( - ExecutionStatus.Completed, - executionOptions, - hadErrors); - } - } - - return executionResult; - } - - /// - /// Executes a PSCommand in the session's runspace without - /// expecting to receive any result. - /// - /// The PSCommand to be executed. - /// - /// An awaitable Task that the caller can use to know when - /// execution completes. - /// - public Task ExecuteCommandAsync(PSCommand psCommand) - { - return this.ExecuteCommandAsync(psCommand); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString) - { - return this.ExecuteScriptStringAsync(scriptString, false, true); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// Error messages from PowerShell will be written to the StringBuilder. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - StringBuilder errorMessages) - { - return this.ExecuteScriptStringAsync(scriptString, errorMessages, false, true, false); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// If true, causes the script string to be written to the host. - /// If true, causes the script output to be written to the host. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - bool writeInputToHost, - bool writeOutputToHost) - { - return this.ExecuteScriptStringAsync(scriptString, null, writeInputToHost, writeOutputToHost, false); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// If true, causes the script string to be written to the host. - /// If true, causes the script output to be written to the host. - /// If true, adds the command to the user's command history. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - bool writeInputToHost, - bool writeOutputToHost, - bool addToHistory) - { - return this.ExecuteScriptStringAsync(scriptString, null, writeInputToHost, writeOutputToHost, addToHistory); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// Error messages from PowerShell will be written to the StringBuilder. - /// If true, causes the script string to be written to the host. - /// If true, causes the script output to be written to the host. - /// If true, adds the command to the user's command history. - /// A Task that can be awaited for the script completion. - public async Task> ExecuteScriptStringAsync( - string scriptString, - StringBuilder errorMessages, - bool writeInputToHost, - bool writeOutputToHost, - bool addToHistory) - { - return await this.ExecuteCommandAsync( - new PSCommand().AddScript(scriptString.Trim()), - errorMessages, - new ExecutionOptions() - { - WriteOutputToHost = writeOutputToHost, - AddToHistory = addToHistory, - WriteInputToHost = writeInputToHost - }); - } - - /// - /// Executes a script file at the specified path. - /// - /// The script execute. - /// Arguments to pass to the script. - /// Writes the executed script path and arguments to the host. - /// A Task that can be awaited for completion. - public async Task ExecuteScriptWithArgsAsync(string script, string arguments = null, bool writeInputToHost = false) - { - PSCommand command = new PSCommand(); - - if (arguments != null) - { - // Need to determine If the script string is a path to a script file. - string scriptAbsPath = string.Empty; - try - { - // Assume we can only debug scripts from the FileSystem provider - string workingDir = (await ExecuteCommandAsync( - new PSCommand() - .AddCommand("Microsoft.PowerShell.Management\\Get-Location") - .AddParameter("PSProvider", "FileSystem"), - false, - false)) - .FirstOrDefault() - .ProviderPath; - - workingDir = workingDir.TrimEnd(Path.DirectorySeparatorChar); - scriptAbsPath = workingDir + Path.DirectorySeparatorChar + script; - } - catch (System.Management.Automation.DriveNotFoundException e) - { - this.logger.Write( - LogLevel.Error, - "Could not determine current filesystem location:\r\n\r\n" + e.ToString()); - } - - var strBld = new StringBuilder(); - - // The script parameter can refer to either a "script path" or a "command name". If it is a - // script path, we can determine that by seeing if the path exists. If so, we always single - // quote that path in case it includes special PowerShell characters like ', &, (, ), [, ] and - // . Any embedded single quotes are escaped. - // If the provided path is already quoted, then File.Exists will not find it. - // This keeps us from quoting an already quoted path. - // Related to issue #123. - if (File.Exists(script) || File.Exists(scriptAbsPath)) - { - // Dot-source the launched script path and single quote the path in case it includes - strBld.Append(". ").Append(QuoteEscapeString(script)); - } - else - { - strBld.Append(script); - } - - // Add arguments - strBld.Append(' ').Append(arguments); - - var launchedScript = strBld.ToString(); - this.logger.Write(LogLevel.Verbose, $"Launch script is: {launchedScript}"); - - command.AddScript(launchedScript, false); - } - else - { - // AddCommand can handle script paths including those with special chars e.g.: - // ".\foo & [bar]\foo.ps1" and it can handle arbitrary commands, like "Invoke-Pester" - command.AddCommand(script, false); - } - - if (writeInputToHost) - { - this.WriteOutput( - script + Environment.NewLine, - true); - } - - await this.ExecuteCommandAsync( - command, - null, - sendOutputToHost: true, - addToHistory: true); - } - - /// - /// Forces the to trigger PowerShell event handling, - /// reliquishing control of the pipeline thread during event processing. - /// - /// - /// This method is called automatically by and - /// . Consider using them instead of this method directly when - /// possible. - /// - internal void ForcePSEventHandling() - { - PromptContext.ForcePSEventHandling(); - } - - /// - /// Marshals a to run on the pipeline thread. A new - /// will be created for the invocation. - /// - /// - /// The to invoke on the pipeline thread. The nested - /// instance for the created - /// will be passed as an argument. - /// - /// - /// An awaitable that the caller can use to know when execution completes. - /// - /// - /// This method is called automatically by . Consider using - /// that method instead of calling this directly when possible. - /// - internal async Task InvokeOnPipelineThreadAsync(Action invocationAction) - { - if (this.PromptNest.IsReadLineBusy()) - { - await this.InvocationEventQueue.InvokeOnPipelineThreadAsync(invocationAction); - return; - } - - // If this is invoked when ReadLine isn't busy then there shouldn't be any running - // pipelines. Right now this method is only used by command completion which doesn't - // actually require running on the pipeline thread, as long as nothing else is running. - invocationAction.Invoke(this.PromptNest.GetPowerShell()); - } - - internal async Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - return await PromptContext.InvokeReadLineAsync( - isCommandLine, - cancellationToken); - } - - internal static TResult ExecuteScriptAndGetItem(string scriptToExecute, Runspace runspace, TResult defaultValue = default(TResult)) - { - using (PowerShell pwsh = PowerShell.Create()) - { - pwsh.Runspace = runspace; - IEnumerable results = pwsh.AddScript(scriptToExecute).Invoke(); - return results.DefaultIfEmpty(defaultValue).First(); - } - } - - /// - /// Loads PowerShell profiles for the host from the specified - /// profile locations. Only the profile paths which exist are - /// loaded. - /// - /// A Task that can be awaited for completion. - public async Task LoadHostProfilesAsync() - { - if (this.profilePaths != null) - { - // Load any of the profile paths that exist - PSCommand command = null; - foreach (var profilePath in this.profilePaths.GetLoadableProfilePaths()) - { - command = new PSCommand(); - command.AddCommand(profilePath, false); - await this.ExecuteCommandAsync(command, true, true); - } - - // Gather the session details (particularly the prompt) after - // loading the user's profiles. - await this.GetSessionDetailsInRunspaceAsync(); - } - } - - /// - /// Causes the most recent execution to be aborted no matter what state - /// it is currently in. - /// - public void AbortExecution() - { - this.AbortExecution(shouldAbortDebugSession: false); - } - - /// - /// Causes the most recent execution to be aborted no matter what state - /// it is currently in. - /// - /// - /// A value indicating whether a debug session should be aborted if one - /// is currently active. - /// - public void AbortExecution(bool shouldAbortDebugSession) - { - if (this.SessionState != PowerShellContextState.Aborting && - this.SessionState != PowerShellContextState.Disposed) - { - this.logger.Write(LogLevel.Verbose, "Execution abort requested..."); - - if (shouldAbortDebugSession) - { - this.ExitAllNestedPrompts(); - } - - if (this.PromptNest.IsInDebugger) - { - if (shouldAbortDebugSession) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - this.ResumeDebugger(DebuggerResumeAction.Stop); - } - else - { - this.versionSpecificOperations.StopCommandInDebugger(this); - } - } - else - { - this.PromptNest.GetPowerShell(isReadLine: false).BeginStop(null, null); - } - - this.SessionState = PowerShellContextState.Aborting; - - this.OnExecutionStatusChanged( - ExecutionStatus.Aborted, - null, - false); - } - else - { - this.logger.Write( - LogLevel.Verbose, - string.Format( - $"Execution abort requested when already aborted (SessionState = {this.SessionState})")); - } - } - - /// - /// Exit all consecutive nested prompts that the user has entered. - /// - internal void ExitAllNestedPrompts() - { - while (this.PromptNest.IsNestedPrompt) - { - this.PromptNest.WaitForCurrentFrameExit(frame => this.ExitNestedPrompt()); - this.versionSpecificOperations.ExitNestedPrompt(ExternalHost); - } - } - - /// - /// Exit all consecutive nested prompts that the user has entered. - /// - /// - /// A task object that represents all nested prompts being exited - /// - internal async Task ExitAllNestedPromptsAsync() - { - while (this.PromptNest.IsNestedPrompt) - { - await this.PromptNest.WaitForCurrentFrameExitAsync(frame => this.ExitNestedPrompt()); - this.versionSpecificOperations.ExitNestedPrompt(ExternalHost); - } - } - - /// - /// Causes the debugger to break execution wherever it currently is. - /// This method is internal because the real Break API is provided - /// by the DebugService. - /// - internal void BreakExecution() - { - this.logger.Write(LogLevel.Verbose, "Debugger break requested..."); - - // Pause the debugger - this.versionSpecificOperations.PauseDebugger( - this.CurrentRunspace.Runspace); - } - - internal void ResumeDebugger(DebuggerResumeAction resumeAction) - { - ResumeDebugger(resumeAction, shouldWaitForExit: true); - } - - private void ResumeDebugger(DebuggerResumeAction resumeAction, bool shouldWaitForExit) - { - resumeRequestHandle.Wait(); - try - { - if (this.PromptNest.IsNestedPrompt) - { - this.ExitAllNestedPrompts(); - } - - if (this.PromptNest.IsInDebugger) - { - // Set the result so that the execution thread resumes. - // The execution thread will clean up the task. - if (shouldWaitForExit) - { - this.PromptNest.WaitForCurrentFrameExit( - frame => - { - frame.ThreadController.StartThreadExit(resumeAction); - this.ConsoleReader?.StopCommandLoop(); - if (this.SessionState != PowerShellContextState.Ready) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - } - }); - } - else - { - this.PromptNest.GetThreadController().StartThreadExit(resumeAction); - this.ConsoleReader?.StopCommandLoop(); - if (this.SessionState != PowerShellContextState.Ready) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - } - } - } - else - { - this.logger.Write( - LogLevel.Error, - $"Tried to resume debugger with action {resumeAction} but there was no debuggerStoppedTask."); - } - } - finally - { - resumeRequestHandle.Release(); - } - } - - /// - /// Disposes the runspace and any other resources being used - /// by this PowerShellContext. - /// - public void Dispose() - { - this.PromptNest.Dispose(); - this.SessionState = PowerShellContextState.Disposed; - - // Clean up the active runspace - this.CleanupRunspace(this.CurrentRunspace); - - // Push the active runspace so it will be included in the loop - this.runspaceStack.Push(this.CurrentRunspace); - - while (this.runspaceStack.Count > 0) - { - RunspaceDetails poppedRunspace = this.runspaceStack.Pop(); - - // Close the popped runspace if it isn't the initial runspace - // or if it is the initial runspace and we own that runspace - if (this.initialRunspace != poppedRunspace || this.ownsInitialRunspace) - { - this.CloseRunspace(poppedRunspace); - } - - this.OnRunspaceChanged( - this, - new RunspaceChangedEventArgs( - RunspaceChangeAction.Shutdown, - poppedRunspace, - null)); - } - - this.initialRunspace = null; - } - - private async Task GetRunspaceHandleAsync(bool isReadLine) - { - return await this.GetRunspaceHandleImplAsync(CancellationToken.None, isReadLine); - } - - private async Task GetRunspaceHandleImplAsync(CancellationToken cancellationToken, bool isReadLine) - { - return await this.PromptNest.GetRunspaceHandleAsync(cancellationToken, isReadLine); - } - - private ExecutionTarget GetExecutionTarget(ExecutionOptions options = null) - { - if (options == null) - { - options = new ExecutionOptions(); - } - - var noBackgroundInvocation = - options.InterruptCommandPrompt || - options.WriteOutputToHost || - options.IsReadLine || - PromptNest.IsRemote; - - // Take over the pipeline if PSReadLine is running, we aren't trying to run PSReadLine, and - // we aren't in a remote session. - if (!noBackgroundInvocation && PromptNest.IsReadLineBusy() && PromptNest.IsMainThreadBusy()) - { - return ExecutionTarget.InvocationEvent; - } - - // We can't take the pipeline from PSReadLine if it's in a remote session, so we need to - // invoke locally in that case. - if (IsDebuggerStopped && PromptNest.IsInDebugger && !(options.IsReadLine && PromptNest.IsRemote)) - { - return ExecutionTarget.Debugger; - } - - return ExecutionTarget.PowerShell; - } - - private bool ShouldExecuteWithEventing(ExecutionOptions executionOptions) - { - return - this.PromptNest.IsReadLineBusy() && - this.PromptNest.IsMainThreadBusy() && - !(executionOptions.IsReadLine || - executionOptions.InterruptCommandPrompt || - executionOptions.WriteOutputToHost || - IsCurrentRunspaceOutOfProcess()); - } - - private void CloseRunspace(RunspaceDetails runspaceDetails) - { - string exitCommand = null; - - switch (runspaceDetails.Context) - { - case RunspaceContext.Original: - if (runspaceDetails.Location == RunspaceLocation.Local) - { - runspaceDetails.Runspace.Close(); - runspaceDetails.Runspace.Dispose(); - } - else - { - exitCommand = "Exit-PSSession"; - } - - break; - - case RunspaceContext.EnteredProcess: - exitCommand = "Exit-PSHostProcess"; - break; - - case RunspaceContext.DebuggedRunspace: - // An attached runspace will be detached when the - // running pipeline is aborted - break; - } - - if (exitCommand != null) - { - Exception exitException = null; - - try - { - using (PowerShell ps = PowerShell.Create()) - { - ps.Runspace = runspaceDetails.Runspace; - ps.AddCommand(exitCommand); - ps.Invoke(); - } - } - catch (RemoteException e) - { - exitException = e; - } - catch (RuntimeException e) - { - exitException = e; - } - - if (exitException != null) - { - this.logger.Write( - LogLevel.Error, - $"Caught {exitException.GetType().Name} while exiting {runspaceDetails.Location} runspace:\r\n{exitException.ToString()}"); - } - } - } - - internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) - { - Validate.IsNotNull("runspaceHandle", runspaceHandle); - - if (PromptNest.IsMainThreadBusy() || (runspaceHandle.IsReadLine && PromptNest.IsReadLineBusy())) - { - var unusedTask = PromptNest - .ReleaseRunspaceHandleAsync(runspaceHandle) - .ConfigureAwait(false); - } - else - { - // Write the situation to the log since this shouldn't happen - this.logger.Write( - LogLevel.Error, - "ReleaseRunspaceHandle was called when the main thread was not busy."); - } - } - - /// - /// Determines if the current runspace is out of process. - /// - /// - /// A value indicating whether the current runspace is out of process. - /// - internal bool IsCurrentRunspaceOutOfProcess() - { - return - CurrentRunspace.Context == RunspaceContext.EnteredProcess || - CurrentRunspace.Context == RunspaceContext.DebuggedRunspace || - CurrentRunspace.Location == RunspaceLocation.Remote; - } - - /// - /// Called by the external PSHost when $Host.EnterNestedPrompt is called. - /// - internal void EnterNestedPrompt() - { - if (this.IsCurrentRunspaceOutOfProcess()) - { - throw new NotSupportedException(); - } - - this.PromptNest.PushPromptContext(PromptNestFrameType.NestedPrompt); - var localThreadController = this.PromptNest.GetThreadController(); - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped, - null)); - - // Reset command loop mainly for PSReadLine - this.ConsoleReader?.StopCommandLoop(); - this.ConsoleReader?.StartCommandLoop(); - - var localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); - var localDebuggerStoppedTask = localThreadController.Exit(); - - // Wait for off-thread pipeline requests and/or ExitNestedPrompt - while (true) - { - int taskIndex = Task.WaitAny( - localPipelineExecutionTask, - localDebuggerStoppedTask); - - if (taskIndex == 0) - { - var localExecutionTask = localPipelineExecutionTask.GetAwaiter().GetResult(); - localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); - localExecutionTask.ExecuteAsync().GetAwaiter().GetResult(); - continue; - } - - this.ConsoleReader?.StopCommandLoop(); - this.PromptNest.PopPromptContext(); - break; - } - } - - /// - /// Called by the external PSHost when $Host.ExitNestedPrompt is called. - /// - internal void ExitNestedPrompt() - { - if (this.PromptNest.NestedPromptLevel == 1 || !this.PromptNest.IsNestedPrompt) - { - this.logger.Write( - LogLevel.Error, - "ExitNestedPrompt was called outside of a nested prompt."); - return; - } - - // Stop the command input loop so PSReadLine isn't invoked between ExitNestedPrompt - // being invoked and EnterNestedPrompt getting the message to exit. - this.ConsoleReader?.StopCommandLoop(); - this.PromptNest.GetThreadController().StartThreadExit(DebuggerResumeAction.Stop); - } - - /// - /// Sets the current working directory of the powershell context. The path should be - /// unescaped before calling this method. - /// - /// - public async Task SetWorkingDirectoryAsync(string path) - { - await this.SetWorkingDirectoryAsync(path, true); - } - - /// - /// Sets the current working directory of the powershell context. - /// - /// - /// Specify false to have the path escaped, otherwise specify true if the path has already been escaped. - public async Task SetWorkingDirectoryAsync(string path, bool isPathAlreadyEscaped) - { - this.InitialWorkingDirectory = path; - - if (!isPathAlreadyEscaped) - { - path = WildcardEscapePath(path); - } - - await ExecuteCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("Path", path), - null, - sendOutputToHost: false, - sendErrorToHost: false, - addToHistory: false); - } - - /// - /// Fully escape a given path for use in PowerShell script. - /// Note: this will not work with PowerShell.AddParameter() - /// - /// The path to escape. - /// An escaped version of the path that can be embedded in PowerShell script. - internal static string FullyPowerShellEscapePath(string path) - { - string wildcardEscapedPath = WildcardEscapePath(path); - return QuoteEscapeString(wildcardEscapedPath); - } - - /// - /// Wrap a string in quotes to make it safe to use in scripts. - /// - /// The glob-escaped path to wrap in quotes. - /// The given path wrapped in quotes appropriately. - internal static string QuoteEscapeString(string escapedPath) - { - var sb = new StringBuilder(escapedPath.Length + 2); // Length of string plus two quotes - sb.Append('\''); - if (!escapedPath.Contains('\'')) - { - sb.Append(escapedPath); - } - else - { - foreach (char c in escapedPath) - { - if (c == '\'') - { - sb.Append("''"); - continue; - } - - sb.Append(c); - } - } - sb.Append('\''); - return sb.ToString(); - } - - /// - /// Return the given path with all PowerShell globbing characters escaped, - /// plus optionally the whitespace. - /// - /// The path to process. - /// Specify True to escape spaces in the path, otherwise False. - /// The path with [ and ] escaped. - internal static string WildcardEscapePath(string path, bool escapeSpaces = false) - { - var sb = new StringBuilder(); - for (int i = 0; i < path.Length; i++) - { - char curr = path[i]; - switch (curr) - { - // Escape '[', ']', '?' and '*' with '`' - case '[': - case ']': - case '*': - case '?': - case '`': - sb.Append('`').Append(curr); - break; - - default: - // Escape whitespace if required - if (escapeSpaces && char.IsWhiteSpace(curr)) - { - sb.Append('`').Append(curr); - break; - } - sb.Append(curr); - break; - } - } - - return sb.ToString(); - } - - /// - /// Returns the passed in path with the [ and ] characters escaped. Escaping spaces is optional. - /// - /// The path to process. - /// Specify True to escape spaces in the path, otherwise False. - /// The path with [ and ] escaped. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This API is not meant for public usage and should not be used.")] - public static string EscapePath(string path, bool escapeSpaces) - { - return WildcardEscapePath(path, escapeSpaces); - } - - internal static string UnescapeWildcardEscapedPath(string wildcardEscapedPath) - { - // Prevent relying on my implementation if we can help it - if (!wildcardEscapedPath.Contains('`')) - { - return wildcardEscapedPath; - } - - var sb = new StringBuilder(wildcardEscapedPath.Length); - for (int i = 0; i < wildcardEscapedPath.Length; i++) - { - // If we see a backtick perform a lookahead - char curr = wildcardEscapedPath[i]; - if (curr == '`' && i + 1 < wildcardEscapedPath.Length) - { - // If the next char is an escapable one, don't add this backtick to the new string - char next = wildcardEscapedPath[i + 1]; - switch (next) - { - case '[': - case ']': - case '?': - case '*': - continue; - - default: - if (char.IsWhiteSpace(next)) - { - continue; - } - break; - } - } - - sb.Append(curr); - } - - return sb.ToString(); - } - - /// - /// Unescapes any escaped [, ] or space characters. Typically use this before calling a - /// .NET API that doesn't understand PowerShell escaped chars. - /// - /// The path to unescape. - /// The path with the ` character before [, ] and spaces removed. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This API is not meant for public usage and should not be used.")] - public static string UnescapePath(string path) - { - return UnescapeWildcardEscapedPath(path); - } - - #endregion - - #region Events - - /// - /// Raised when the state of the session has changed. - /// - public event EventHandler SessionStateChanged; - - private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e) - { - if (this.SessionState != PowerShellContextState.Disposed) - { - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Session state changed --\r\n\r\n Old state: {0}\r\n New state: {1}\r\n Result: {2}", - this.SessionState.ToString(), - e.NewSessionState.ToString(), - e.ExecutionResult)); - - this.SessionState = e.NewSessionState; - this.SessionStateChanged?.Invoke(sender, e); - } - else - { - this.logger.Write( - LogLevel.Warning, - $"Received session state change to {e.NewSessionState} when already disposed"); - } - } - - /// - /// Raised when the runspace changes by entering a remote session or one in a different process. - /// - public event EventHandler RunspaceChanged; - - private void OnRunspaceChanged(object sender, RunspaceChangedEventArgs e) - { - this.RunspaceChanged?.Invoke(sender, e); - } - - /// - /// Raised when the status of an executed command changes. - /// - public event EventHandler ExecutionStatusChanged; - - private void OnExecutionStatusChanged( - ExecutionStatus executionStatus, - ExecutionOptions executionOptions, - bool hadErrors) - { - this.ExecutionStatusChanged?.Invoke( - this, - new ExecutionStatusChangedEventArgs( - executionStatus, - executionOptions, - hadErrors)); - } - - #endregion - - #region Private Methods - - private IEnumerable ExecuteCommandInDebugger(PSCommand psCommand, bool sendOutputToHost) - { - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Attempting to execute command(s) in the debugger:\r\n\r\n{0}", - GetStringForPSCommand(psCommand))); - - IEnumerable output = - this.versionSpecificOperations.ExecuteCommandInDebugger( - this, - this.CurrentRunspace.Runspace, - psCommand, - sendOutputToHost, - out DebuggerResumeAction? debuggerResumeAction); - - if (debuggerResumeAction.HasValue) - { - // Resume the debugger with the specificed action - this.ResumeDebugger( - debuggerResumeAction.Value, - shouldWaitForExit: false); - } - - return output; - } - - internal void WriteOutput(string outputString, bool includeNewLine) - { - this.WriteOutput( - outputString, - includeNewLine, - OutputType.Normal); - } - - internal void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType) - { - if (this.ConsoleWriter != null) - { - this.ConsoleWriter.WriteOutput( - outputString, - includeNewLine, - outputType); - } - } - - private void WriteExceptionToHost(Exception e) - { - const string ExceptionFormat = - "{0}\r\n{1}\r\n + CategoryInfo : {2}\r\n + FullyQualifiedErrorId : {3}"; - - IContainsErrorRecord containsErrorRecord = e as IContainsErrorRecord; - - if (containsErrorRecord == null || - containsErrorRecord.ErrorRecord == null) - { - this.WriteError(e.Message, null, 0, 0); - return; - } - - ErrorRecord errorRecord = containsErrorRecord.ErrorRecord; - if (errorRecord.InvocationInfo == null) - { - this.WriteError(errorRecord.ToString(), String.Empty, 0, 0); - return; - } - - string errorRecordString = errorRecord.ToString(); - if ((errorRecord.InvocationInfo.PositionMessage != null) && - errorRecordString.IndexOf(errorRecord.InvocationInfo.PositionMessage, StringComparison.Ordinal) != -1) - { - this.WriteError(errorRecordString); - return; - } - - string message = - string.Format( - CultureInfo.InvariantCulture, - ExceptionFormat, - errorRecord.ToString(), - errorRecord.InvocationInfo.PositionMessage, - errorRecord.CategoryInfo, - errorRecord.FullyQualifiedErrorId); - - this.WriteError(message); - } - - private void WriteError( - string errorMessage, - string filePath, - int lineNumber, - int columnNumber) - { - const string ErrorLocationFormat = "At {0}:{1} char:{2}"; - - this.WriteError( - errorMessage + - Environment.NewLine + - string.Format( - ErrorLocationFormat, - String.IsNullOrEmpty(filePath) ? "line" : filePath, - lineNumber, - columnNumber)); - } - - private void WriteError(string errorMessage) - { - if (this.ConsoleWriter != null) - { - this.ConsoleWriter.WriteOutput( - errorMessage, - true, - OutputType.Error, - ConsoleColor.Red, - ConsoleColor.Black); - } - } - - void powerShell_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e) - { - SessionStateChangedEventArgs eventArgs = TranslateInvocationStateInfo(e.InvocationStateInfo); - this.OnSessionStateChanged(this, eventArgs); - } - - private static SessionStateChangedEventArgs TranslateInvocationStateInfo(PSInvocationStateInfo invocationState) - { - PowerShellContextState newState = PowerShellContextState.Unknown; - PowerShellExecutionResult executionResult = PowerShellExecutionResult.NotFinished; - - switch (invocationState.State) - { - case PSInvocationState.NotStarted: - newState = PowerShellContextState.NotStarted; - break; - - case PSInvocationState.Failed: - newState = PowerShellContextState.Ready; - executionResult = PowerShellExecutionResult.Failed; - break; - - case PSInvocationState.Disconnected: - // TODO: Any extra work to do in this case? - // TODO: Is this a unique state that can be re-connected? - newState = PowerShellContextState.Disposed; - executionResult = PowerShellExecutionResult.Stopped; - break; - - case PSInvocationState.Running: - newState = PowerShellContextState.Running; - break; - - case PSInvocationState.Completed: - newState = PowerShellContextState.Ready; - executionResult = PowerShellExecutionResult.Completed; - break; - - case PSInvocationState.Stopping: - newState = PowerShellContextState.Aborting; - break; - - case PSInvocationState.Stopped: - newState = PowerShellContextState.Ready; - executionResult = PowerShellExecutionResult.Aborted; - break; - - default: - newState = PowerShellContextState.Unknown; - break; - } - - return - new SessionStateChangedEventArgs( - newState, - executionResult, - invocationState.Reason); - } - - private Command GetOutputCommand(bool endOfStatement) - { - Command outputCommand = - new Command( - command: this.PromptNest.IsInDebugger ? "Out-String" : "Out-Default", - isScript: false, - useLocalScope: true); - - if (this.PromptNest.IsInDebugger) - { - // Out-String needs the -Stream parameter added - outputCommand.Parameters.Add("Stream"); - } - - return outputCommand; - } - - private static string GetStringForPSCommand(PSCommand psCommand) - { - StringBuilder stringBuilder = new StringBuilder(); - - foreach (var command in psCommand.Commands) - { - stringBuilder.Append(" "); - stringBuilder.Append(command.CommandText); - foreach (var param in command.Parameters) - { - if (param.Name != null) - { - stringBuilder.Append($" -{param.Name} {param.Value}"); - } - else - { - stringBuilder.Append($" {param.Value}"); - } - } - - stringBuilder.AppendLine(); - } - - return stringBuilder.ToString(); - } - - private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) - { - var currentPolicy = ExecutionPolicy.Undefined; - - // Get the current execution policy so that we don't set it higher than it already is - this.powerShell.Commands.AddCommand("Get-ExecutionPolicy"); - - var result = this.powerShell.Invoke(); - if (result.Count > 0) - { - currentPolicy = result.FirstOrDefault(); - } - - if (desiredExecutionPolicy < currentPolicy || - desiredExecutionPolicy == ExecutionPolicy.Bypass || - currentPolicy == ExecutionPolicy.Undefined) - { - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Setting execution policy:\r\n Current = ExecutionPolicy.{0}\r\n Desired = ExecutionPolicy.{1}", - currentPolicy, - desiredExecutionPolicy)); - - this.powerShell.Commands.Clear(); - this.powerShell - .AddCommand("Set-ExecutionPolicy") - .AddParameter("ExecutionPolicy", desiredExecutionPolicy) - .AddParameter("Scope", ExecutionPolicyScope.Process) - .AddParameter("Force"); - - try - { - this.powerShell.Invoke(); - } - catch (CmdletInvocationException e) - { - this.logger.WriteException( - $"An error occurred while calling Set-ExecutionPolicy, the desired policy of {desiredExecutionPolicy} may not be set.", - e); - } - - this.powerShell.Commands.Clear(); - } - else - { - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Current execution policy: ExecutionPolicy.{0}", - currentPolicy)); - - } - } - - private SessionDetails GetSessionDetails(Func invokeAction) - { - try - { - this.mostRecentSessionDetails = - new SessionDetails( - invokeAction( - SessionDetails.GetDetailsCommand())); - - return this.mostRecentSessionDetails; - } - catch (RuntimeException e) - { - this.logger.Write( - LogLevel.Verbose, - "Runtime exception occurred while gathering runspace info:\r\n\r\n" + e.ToString()); - } - catch (ArgumentNullException) - { - this.logger.Write( - LogLevel.Error, - "Could not retrieve session details but no exception was thrown."); - } - - // TODO: Return a harmless object if necessary - this.mostRecentSessionDetails = null; - return this.mostRecentSessionDetails; - } - - private async Task GetSessionDetailsInRunspaceAsync() - { - using (RunspaceHandle runspaceHandle = await this.GetRunspaceHandleAsync()) - { - return this.GetSessionDetailsInRunspace(runspaceHandle.Runspace); - } - } - - private SessionDetails GetSessionDetailsInRunspace(Runspace runspace) - { - SessionDetails sessionDetails = - this.GetSessionDetails( - command => - { - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspace; - powerShell.Commands = command; - - return - powerShell - .Invoke() - .FirstOrDefault(); - } - }); - - return sessionDetails; - } - - private SessionDetails GetSessionDetailsInDebugger() - { - return this.GetSessionDetails( - command => - { - // Use LastOrDefault to get the last item returned. This - // is necessary because advanced prompt functions (like those - // in posh-git) may return multiple objects in the result. - return - this.ExecuteCommandInDebugger(command, false) - .LastOrDefault(); - }); - } - - private SessionDetails GetSessionDetailsInNestedPipeline() - { - // We don't need to check what thread we're on here. If it's a local - // nested pipeline then we will already be on the correct thread, and - // non-debugger nested pipelines aren't supported in remote runspaces. - return this.GetSessionDetails( - command => - { - using (var localPwsh = PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - localPwsh.Commands = command; - return localPwsh.Invoke().FirstOrDefault(); - } - }); - } - - private void SetProfileVariableInCurrentRunspace(ProfilePaths profilePaths) - { - // Create the $profile variable - PSObject profile = new PSObject(profilePaths.CurrentUserCurrentHost); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.AllUsersAllHosts), - profilePaths.AllUsersAllHosts)); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.AllUsersCurrentHost), - profilePaths.AllUsersCurrentHost)); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.CurrentUserAllHosts), - profilePaths.CurrentUserAllHosts)); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.CurrentUserCurrentHost), - profilePaths.CurrentUserCurrentHost)); - - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Setting $profile variable in runspace. Current user host profile path: {0}", - profilePaths.CurrentUserCurrentHost)); - - // Set the variable in the runspace - this.powerShell.Commands.Clear(); - this.powerShell - .AddCommand("Set-Variable") - .AddParameter("Name", "profile") - .AddParameter("Value", profile) - .AddParameter("Option", "None"); - this.powerShell.Invoke(); - this.powerShell.Commands.Clear(); - } - - private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs args) - { - switch (args.RunspaceStateInfo.State) - { - case RunspaceState.Opening: - case RunspaceState.Opened: - // These cases don't matter, just return - return; - - case RunspaceState.Closing: - case RunspaceState.Closed: - case RunspaceState.Broken: - // If the runspace closes or fails, pop the runspace - ((IHostSupportsInteractiveSession)this).PopRunspace(); - break; - } - } - - #endregion - - #region Events - - // NOTE: This event is 'internal' because the DebugService provides - // the publicly consumable event. - internal event EventHandler DebuggerStop; - - /// - /// Raised when the debugger is resumed after it was previously stopped. - /// - public event EventHandler DebuggerResumed; - - private void StartCommandLoopOnRunspaceAvailable() - { - if (Interlocked.CompareExchange(ref this.isCommandLoopRestarterSet, 1, 1) == 1) - { - return; - } - - EventHandler handler = null; - handler = (runspace, eventArgs) => - { - if (eventArgs.RunspaceAvailability != RunspaceAvailability.Available || - this.versionSpecificOperations.IsDebuggerStopped(this.PromptNest, (Runspace)runspace)) - { - return; - } - - ((Runspace)runspace).AvailabilityChanged -= handler; - Interlocked.Exchange(ref this.isCommandLoopRestarterSet, 0); - this.ConsoleReader?.StartCommandLoop(); - }; - - this.CurrentRunspace.Runspace.AvailabilityChanged += handler; - Interlocked.Exchange(ref this.isCommandLoopRestarterSet, 1); - } - - private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) - { - if (CurrentRunspace.Context == RunspaceContext.Original) - { - StartCommandLoopOnRunspaceAvailable(); - } - - this.logger.Write(LogLevel.Verbose, "Debugger stopped execution."); - - PromptNest.PushPromptContext( - IsCurrentRunspaceOutOfProcess() - ? PromptNestFrameType.Debug | PromptNestFrameType.Remote - : PromptNestFrameType.Debug); - - ThreadController localThreadController = PromptNest.GetThreadController(); - - // Update the session state - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped, - null)); - - // Get the session details and push the current - // runspace if the session has changed - SessionDetails sessionDetails = null; - try - { - sessionDetails = this.GetSessionDetailsInDebugger(); - } - catch (InvalidOperationException) - { - this.logger.Write( - LogLevel.Verbose, - "Attempting to get session details failed, most likely due to a running pipeline that is attempting to stop."); - } - - if (!localThreadController.FrameExitTask.Task.IsCompleted) - { - // Push the current runspace if the session has changed - this.UpdateRunspaceDetailsIfSessionChanged(sessionDetails, isDebuggerStop: true); - - // Raise the event for the debugger service - this.DebuggerStop?.Invoke(sender, e); - } - - this.logger.Write(LogLevel.Verbose, "Starting pipeline thread message loop..."); - - Task localPipelineExecutionTask = - localThreadController.TakeExecutionRequestAsync(); - Task localDebuggerStoppedTask = - localThreadController.Exit(); - while (true) - { - int taskIndex = - Task.WaitAny( - localDebuggerStoppedTask, - localPipelineExecutionTask); - - if (taskIndex == 0) - { - // Write a new output line before continuing - this.WriteOutput("", true); - - e.ResumeAction = localDebuggerStoppedTask.GetAwaiter().GetResult(); - this.logger.Write(LogLevel.Verbose, "Received debugger resume action " + e.ResumeAction.ToString()); - - // Notify listeners that the debugger has resumed - this.DebuggerResumed?.Invoke(this, e.ResumeAction); - - // Pop the current RunspaceDetails if we were attached - // to a runspace and the resume action is Stop - if (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace && - e.ResumeAction == DebuggerResumeAction.Stop) - { - this.PopRunspace(); - } - else if (e.ResumeAction != DebuggerResumeAction.Stop) - { - // Update the session state - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Running, - PowerShellExecutionResult.NotFinished, - null)); - } - - break; - } - else if (taskIndex == 1) - { - this.logger.Write(LogLevel.Verbose, "Received pipeline thread execution request."); - - IPipelineExecutionRequest executionRequest = localPipelineExecutionTask.Result; - localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); - executionRequest.ExecuteAsync().GetAwaiter().GetResult(); - - this.logger.Write(LogLevel.Verbose, "Pipeline thread execution completed."); - - if (!this.versionSpecificOperations.IsDebuggerStopped( - this.PromptNest, - this.CurrentRunspace.Runspace)) - { - if (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace) - { - // Notify listeners that the debugger has resumed - this.DebuggerResumed?.Invoke(this, DebuggerResumeAction.Stop); - - // We're detached from the runspace now, send a runspace update. - this.PopRunspace(); - } - - // If the executed command caused the debugger to exit, break - // from the pipeline loop - break; - } - } - else - { - // TODO: How to handle this? - } - } - - PromptNest.PopPromptContext(); - } - - // NOTE: This event is 'internal' because the DebugService provides - // the publicly consumable event. - internal event EventHandler BreakpointUpdated; - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) - { - this.BreakpointUpdated?.Invoke(sender, e); - } - - #endregion - - #region Nested Classes - - private void ConfigureRunspaceCapabilities(RunspaceDetails runspaceDetails) - { - DscBreakpointCapability.CheckForCapability(this.CurrentRunspace, this, this.logger); - } - - private void PushRunspace(RunspaceDetails newRunspaceDetails) - { - this.logger.Write( - LogLevel.Verbose, - $"Pushing {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), new runspace is {newRunspaceDetails.Location} ({newRunspaceDetails.Context}), connection: {newRunspaceDetails.ConnectionString}"); - - RunspaceDetails previousRunspace = this.CurrentRunspace; - - if (newRunspaceDetails.Context == RunspaceContext.DebuggedRunspace) - { - this.WriteOutput( - $"Entering debugged runspace on {newRunspaceDetails.Location.ToString().ToLower()} machine {newRunspaceDetails.SessionDetails.ComputerName}", - true); - } - - // Switch out event handlers if necessary - if (CheckIfRunspaceNeedsEventHandlers(newRunspaceDetails)) - { - this.CleanupRunspace(previousRunspace); - this.ConfigureRunspace(newRunspaceDetails); - } - - this.runspaceStack.Push(previousRunspace); - this.CurrentRunspace = newRunspaceDetails; - - // Check for runspace capabilities - this.ConfigureRunspaceCapabilities(newRunspaceDetails); - - this.OnRunspaceChanged( - this, - new RunspaceChangedEventArgs( - RunspaceChangeAction.Enter, - previousRunspace, - this.CurrentRunspace)); - } - - private void UpdateRunspaceDetailsIfSessionChanged(SessionDetails sessionDetails, bool isDebuggerStop = false) - { - RunspaceDetails newRunspaceDetails = null; - - // If we've exited an entered process or debugged runspace, pop what we've - // got before we evaluate where we're at - if ( - (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace && - this.CurrentRunspace.SessionDetails.InstanceId != sessionDetails.InstanceId) || - (this.CurrentRunspace.Context == RunspaceContext.EnteredProcess && - this.CurrentRunspace.SessionDetails.ProcessId != sessionDetails.ProcessId)) - { - this.PopRunspace(); - } - - // Are we in a new session that the PushRunspace command won't - // notify us about? - // - // Possible cases: - // - Debugged runspace in a local or remote session - // - Entered process in a remote session - // - // We don't need additional logic to check for the cases that - // PowerShell would have notified us about because the CurrentRunspace - // will already be updated by PowerShell by the time we reach - // these checks. - - if (this.CurrentRunspace.SessionDetails.InstanceId != sessionDetails.InstanceId && isDebuggerStop) - { - // Are we on a local or remote computer? - bool differentComputer = - !string.Equals( - sessionDetails.ComputerName, - this.initialRunspace.SessionDetails.ComputerName, - StringComparison.CurrentCultureIgnoreCase); - - // We started debugging a runspace - newRunspaceDetails = - RunspaceDetails.CreateFromDebugger( - this.CurrentRunspace, - differentComputer ? RunspaceLocation.Remote : RunspaceLocation.Local, - RunspaceContext.DebuggedRunspace, - sessionDetails); - } - else if (this.CurrentRunspace.SessionDetails.ProcessId != sessionDetails.ProcessId) - { - // We entered a different PowerShell host process - newRunspaceDetails = - RunspaceDetails.CreateFromContext( - this.CurrentRunspace, - RunspaceContext.EnteredProcess, - sessionDetails); - } - - if (newRunspaceDetails != null) - { - this.PushRunspace(newRunspaceDetails); - } - } - - private void PopRunspace() - { - if (this.SessionState != PowerShellContextState.Disposed) - { - if (this.runspaceStack.Count > 0) - { - RunspaceDetails previousRunspace = this.CurrentRunspace; - this.CurrentRunspace = this.runspaceStack.Pop(); - - this.logger.Write( - LogLevel.Verbose, - $"Popping {previousRunspace.Location} ({previousRunspace.Context}), new runspace is {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), connection: {this.CurrentRunspace.ConnectionString}"); - - if (previousRunspace.Context == RunspaceContext.DebuggedRunspace) - { - this.WriteOutput( - $"Leaving debugged runspace on {previousRunspace.Location.ToString().ToLower()} machine {previousRunspace.SessionDetails.ComputerName}", - true); - } - - // Switch out event handlers if necessary - if (CheckIfRunspaceNeedsEventHandlers(previousRunspace)) - { - this.CleanupRunspace(previousRunspace); - this.ConfigureRunspace(this.CurrentRunspace); - } - - this.OnRunspaceChanged( - this, - new RunspaceChangedEventArgs( - RunspaceChangeAction.Exit, - previousRunspace, - this.CurrentRunspace)); - } - else - { - this.logger.Write( - LogLevel.Error, - "Caller attempted to pop a runspace when no runspaces are on the stack."); - } - } - } - - #endregion - - #region IHostSupportsInteractiveSession Implementation - - bool IHostSupportsInteractiveSession.IsRunspacePushed - { - get - { - return this.runspaceStack.Count > 0; - } - } - - Runspace IHostSupportsInteractiveSession.Runspace - { - get - { - return this.CurrentRunspace.Runspace; - } - } - - void IHostSupportsInteractiveSession.PushRunspace(Runspace runspace) - { - // Get the session details for the new runspace - SessionDetails sessionDetails = this.GetSessionDetailsInRunspace(runspace); - - this.PushRunspace( - RunspaceDetails.CreateFromRunspace( - runspace, - sessionDetails, - this.logger)); - } - - void IHostSupportsInteractiveSession.PopRunspace() - { - this.PopRunspace(); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/PowerShellContextState.cs b/src/PowerShellEditorServices/Session/PowerShellContextState.cs deleted file mode 100644 index 2075c04d5..000000000 --- a/src/PowerShellEditorServices/Session/PowerShellContextState.cs +++ /dev/null @@ -1,47 +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 -{ - /// - /// Enumerates the possible states for a PowerShellContext. - /// - public enum PowerShellContextState - { - /// - /// Indicates an unknown, potentially uninitialized state. - /// - Unknown = 0, - - /// - /// Indicates the state where the session is starting but - /// not yet fully initialized. - /// - NotStarted, - - /// - /// Indicates that the session is ready to accept commands - /// for execution. - /// - Ready, - - /// - /// Indicates that the session is currently running a command. - /// - Running, - - /// - /// Indicates that the session is aborting the current execution. - /// - Aborting, - - /// - /// Indicates that the session is already disposed and cannot - /// accept further execution requests. - /// - Disposed - } -} - diff --git a/src/PowerShellEditorServices/Session/PowerShellExecutionResult.cs b/src/PowerShellEditorServices/Session/PowerShellExecutionResult.cs deleted file mode 100644 index ee15ae97a..000000000 --- a/src/PowerShellEditorServices/Session/PowerShellExecutionResult.cs +++ /dev/null @@ -1,40 +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 -{ - /// - /// Enumerates the possible execution results that can occur after - /// executing a command or script. - /// - public enum PowerShellExecutionResult - { - /// - /// Indicates that execution is not yet finished. - /// - NotFinished, - - /// - /// Indicates that execution has failed. - /// - Failed, - - /// - /// Indicates that execution was aborted by the user. - /// - Aborted, - - /// - /// Indicates that execution was stopped by the debugger. - /// - Stopped, - - /// - /// Indicates that execution completed successfully. - /// - Completed - } -} - diff --git a/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs deleted file mode 100644 index efd5e5feb..000000000 --- a/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs +++ /dev/null @@ -1,167 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Defines the possible enumeration values for the PowerShell process architecture. - /// - public enum PowerShellProcessArchitecture - { - /// - /// The processor architecture is unknown or wasn't accessible. - /// - Unknown, - - /// - /// The processor architecture is 32-bit. - /// - X86, - - /// - /// The processor architecture is 64-bit. - /// - X64 - } - - /// - /// Provides details about the version of the PowerShell runtime. - /// - public class PowerShellVersionDetails - { - #region Properties - - /// - /// Gets the version of the PowerShell runtime. - /// - public Version Version { get; private set; } - - /// - /// Gets the full version string, either the ToString of the Version - /// property or the GitCommitId for open-source PowerShell releases. - /// - public string VersionString { get; private set; } - - /// - /// Gets the PowerShell edition (generally Desktop or Core). - /// - public string Edition { get; private set; } - - /// - /// Gets the architecture of the PowerShell process. - /// - public PowerShellProcessArchitecture Architecture { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the PowerShellVersionDetails class. - /// - /// The version of the PowerShell runtime. - /// A string representation of the PowerShell version. - /// The string representation of the PowerShell edition. - /// The processor architecture. - public PowerShellVersionDetails( - Version version, - string versionString, - string editionString, - PowerShellProcessArchitecture architecture) - { - this.Version = version; - this.VersionString = versionString; - this.Edition = editionString; - this.Architecture = architecture; - } - - #endregion - - #region Public Methods - - /// - /// Gets the PowerShell version details for the given runspace. - /// - /// The runspace for which version details will be gathered. - /// An ILogger implementation used for writing log messages. - /// A new PowerShellVersionDetails instance. - public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILogger logger) - { - Version powerShellVersion = new Version(5, 0); - string versionString = null; - string powerShellEdition = "Desktop"; - var architecture = PowerShellProcessArchitecture.Unknown; - - try - { - var psVersionTable = PowerShellContext.ExecuteScriptAndGetItem("$PSVersionTable", runspace); - if (psVersionTable != null) - { - var edition = psVersionTable["PSEdition"] as string; - if (edition != null) - { - powerShellEdition = edition; - } - - // The PSVersion value will either be of Version or SemanticVersion. - // In the former case, take the value directly. In the latter case, - // generate a Version from its string representation. - var version = psVersionTable["PSVersion"]; - if (version is Version) - { - powerShellVersion = (Version)version; - } - else if (version != null) - { - // Expected version string format is 6.0.0-alpha so build a simpler version from that - powerShellVersion = new Version(version.ToString().Split('-')[0]); - } - - var gitCommitId = psVersionTable["GitCommitId"] as string; - if (gitCommitId != null) - { - versionString = gitCommitId; - } - else - { - versionString = powerShellVersion.ToString(); - } - - var arch = PowerShellContext.ExecuteScriptAndGetItem("$env:PROCESSOR_ARCHITECTURE", runspace); - if (arch != null) - { - if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase)) - { - architecture = PowerShellProcessArchitecture.X64; - } - else if (string.Equals(arch, "x86", StringComparison.CurrentCultureIgnoreCase)) - { - architecture = PowerShellProcessArchitecture.X86; - } - } - } - } - catch (Exception ex) - { - logger.Write( - LogLevel.Warning, - "Failed to look up PowerShell version, defaulting to version 5.\r\n\r\n" + ex.ToString()); - } - - return new PowerShellVersionDetails( - powerShellVersion, - versionString, - powerShellEdition, - architecture); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/ProfilePaths.cs b/src/PowerShellEditorServices/Session/ProfilePaths.cs deleted file mode 100644 index ef94092ec..000000000 --- a/src/PowerShellEditorServices/Session/ProfilePaths.cs +++ /dev/null @@ -1,110 +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.IO; -using System.Linq; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Provides profile path resolution behavior relative to the name - /// of a particular PowerShell host. - /// - public class ProfilePaths - { - #region Constants - - /// - /// The file name for the "all hosts" profile. Also used as the - /// suffix for the host-specific profile filenames. - /// - public const string AllHostsProfileName = "profile.ps1"; - - #endregion - - #region Properties - - /// - /// Gets the profile path for all users, all hosts. - /// - public string AllUsersAllHosts { get; private set; } - - /// - /// Gets the profile path for all users, current host. - /// - public string AllUsersCurrentHost { get; private set; } - - /// - /// Gets the profile path for the current user, all hosts. - /// - public string CurrentUserAllHosts { get; private set; } - - /// - /// Gets the profile path for the current user and host. - /// - public string CurrentUserCurrentHost { get; private set; } - - #endregion - - #region Public Methods - - /// - /// Creates a new instance of the ProfilePaths class. - /// - /// - /// The identifier of the host used in the host-specific X_profile.ps1 filename. - /// - /// The base path to use for constructing AllUsers profile paths. - /// The base path to use for constructing CurrentUser profile paths. - public ProfilePaths( - string hostProfileId, - string baseAllUsersPath, - string baseCurrentUserPath) - { - this.Initialize(hostProfileId, baseAllUsersPath, baseCurrentUserPath); - } - - private void Initialize( - string hostProfileId, - string baseAllUsersPath, - string baseCurrentUserPath) - { - string currentHostProfileName = - string.Format( - "{0}_{1}", - hostProfileId, - AllHostsProfileName); - - this.AllUsersCurrentHost = Path.Combine(baseAllUsersPath, currentHostProfileName); - this.CurrentUserCurrentHost = Path.Combine(baseCurrentUserPath, currentHostProfileName); - this.AllUsersAllHosts = Path.Combine(baseAllUsersPath, AllHostsProfileName); - this.CurrentUserAllHosts = Path.Combine(baseCurrentUserPath, AllHostsProfileName); - } - - /// - /// Gets the list of profile paths that exist on the filesystem. - /// - /// An IEnumerable of profile path strings to be loaded. - public IEnumerable GetLoadableProfilePaths() - { - var profilePaths = - new string[] - { - this.AllUsersAllHosts, - this.AllUsersCurrentHost, - this.CurrentUserAllHosts, - this.CurrentUserCurrentHost - }; - - return profilePaths.Where(p => File.Exists(p)); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Session/ProgressDetails.cs b/src/PowerShellEditorServices/Session/ProgressDetails.cs deleted file mode 100644 index b88d7c1ae..000000000 --- a/src/PowerShellEditorServices/Session/ProgressDetails.cs +++ /dev/null @@ -1,33 +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.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides details about the progress of a particular activity. - /// - public class ProgressDetails - { - /// - /// Gets the percentage of the activity that has been completed. - /// - public int PercentComplete { get; private set; } - - internal static ProgressDetails Create(ProgressRecord progressRecord) - { - //progressRecord.RecordType == ProgressRecordType.Completed; - //progressRecord.Activity; - //progressRecord. - - return new ProgressDetails - { - PercentComplete = progressRecord.PercentComplete - }; - } - } -} - diff --git a/src/PowerShellEditorServices/Session/PromptNest.cs b/src/PowerShellEditorServices/Session/PromptNest.cs deleted file mode 100644 index 9cf4437f2..000000000 --- a/src/PowerShellEditorServices/Session/PromptNest.cs +++ /dev/null @@ -1,564 +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.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - using System; - using System.Management.Automation; - - /// - /// Represents the stack of contexts in which PowerShell commands can be invoked. - /// - internal class PromptNest : IDisposable - { - private ConcurrentStack _frameStack; - - private PromptNestFrame _readLineFrame; - - private IHostInput _consoleReader; - - private PowerShellContext _powerShellContext; - - private IVersionSpecificOperations _versionSpecificOperations; - - private bool _isDisposed; - - private object _syncObject = new object(); - - private object _disposeSyncObject = new object(); - - /// - /// Initializes a new instance of the class. - /// - /// - /// The to track prompt status for. - /// - /// - /// The instance for the first frame. - /// - /// - /// The input handler. - /// - /// - /// The for the calling - /// instance. - /// - /// - /// This constructor should only be called when - /// is set to the initial runspace. - /// - internal PromptNest( - PowerShellContext powerShellContext, - PowerShell initialPowerShell, - IHostInput consoleReader, - IVersionSpecificOperations versionSpecificOperations) - { - _versionSpecificOperations = versionSpecificOperations; - _consoleReader = consoleReader; - _powerShellContext = powerShellContext; - _frameStack = new ConcurrentStack(); - _frameStack.Push( - new PromptNestFrame( - initialPowerShell, - NewHandleQueue())); - - var readLineShell = PowerShell.Create(); - readLineShell.Runspace = powerShellContext.CurrentRunspace.Runspace; - _readLineFrame = new PromptNestFrame( - readLineShell, - new AsyncQueue()); - - ReleaseRunspaceHandleImpl(isReadLine: true); - } - - /// - /// Gets a value indicating whether the current frame was created by a debugger stop event. - /// - internal bool IsInDebugger => CurrentFrame.FrameType.HasFlag(PromptNestFrameType.Debug); - - /// - /// Gets a value indicating whether the current frame was created for an out of process runspace. - /// - internal bool IsRemote => CurrentFrame.FrameType.HasFlag(PromptNestFrameType.Remote); - - /// - /// Gets a value indicating whether the current frame was created by PSHost.EnterNestedPrompt(). - /// - internal bool IsNestedPrompt => CurrentFrame.FrameType.HasFlag(PromptNestFrameType.NestedPrompt); - - /// - /// Gets a value indicating the current number of frames managed by this PromptNest. - /// - internal int NestedPromptLevel => _frameStack.Count; - - private PromptNestFrame CurrentFrame - { - get - { - _frameStack.TryPeek(out PromptNestFrame currentFrame); - return _isDisposed ? _readLineFrame : currentFrame; - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - lock (_disposeSyncObject) - { - if (_isDisposed || !disposing) - { - return; - } - - while (NestedPromptLevel > 1) - { - _consoleReader?.StopCommandLoop(); - var currentFrame = CurrentFrame; - if (currentFrame.FrameType.HasFlag(PromptNestFrameType.Debug)) - { - _versionSpecificOperations.StopCommandInDebugger(_powerShellContext); - currentFrame.ThreadController.StartThreadExit(DebuggerResumeAction.Stop); - currentFrame.WaitForFrameExit(CancellationToken.None); - continue; - } - - if (currentFrame.FrameType.HasFlag(PromptNestFrameType.NestedPrompt)) - { - _powerShellContext.ExitAllNestedPrompts(); - continue; - } - - currentFrame.PowerShell.BeginStop(null, null); - currentFrame.WaitForFrameExit(CancellationToken.None); - } - - _consoleReader?.StopCommandLoop(); - _readLineFrame.Dispose(); - CurrentFrame.Dispose(); - _frameStack.Clear(); - _powerShellContext = null; - _consoleReader = null; - _isDisposed = true; - } - } - - /// - /// Gets the for the current frame. - /// - /// - /// The for the current frame, or - /// if the current frame does not have one. - /// - internal ThreadController GetThreadController() - { - if (_isDisposed) - { - return null; - } - - return CurrentFrame.IsThreadController ? CurrentFrame.ThreadController : null; - } - - /// - /// Create a new and set it as the current frame. - /// - internal void PushPromptContext() - { - if (_isDisposed) - { - return; - } - - PushPromptContext(PromptNestFrameType.Normal); - } - - /// - /// Create a new and set it as the current frame. - /// - /// The frame type. - internal void PushPromptContext(PromptNestFrameType frameType) - { - if (_isDisposed) - { - return; - } - - _frameStack.Push( - new PromptNestFrame( - frameType.HasFlag(PromptNestFrameType.Remote) - ? PowerShell.Create() - : PowerShell.Create(RunspaceMode.CurrentRunspace), - NewHandleQueue(), - frameType)); - } - - /// - /// Dispose of the current and revert to the previous frame. - /// - internal void PopPromptContext() - { - PromptNestFrame currentFrame; - lock (_syncObject) - { - if (_isDisposed || _frameStack.Count == 1) - { - return; - } - - _frameStack.TryPop(out currentFrame); - } - - currentFrame.Dispose(); - } - - /// - /// Get the instance for the current - /// . - /// - /// Indicates whether this is for a PSReadLine command. - /// The instance for the current frame. - internal PowerShell GetPowerShell(bool isReadLine = false) - { - if (_isDisposed) - { - return null; - } - - // Typically we want to run PSReadLine on the current nest frame. - // The exception is when the current frame is remote, in which - // case we need to run it in it's own frame because we can't take - // over a remote pipeline through event invocation. - if (NestedPromptLevel > 1 && !IsRemote) - { - return CurrentFrame.PowerShell; - } - - return isReadLine ? _readLineFrame.PowerShell : CurrentFrame.PowerShell; - } - - /// - /// Get the for the current . - /// - /// - /// The that can be used to cancel the request. - /// - /// Indicates whether this is for a PSReadLine command. - /// The for the current frame. - internal RunspaceHandle GetRunspaceHandle(CancellationToken cancellationToken, bool isReadLine) - { - if (_isDisposed) - { - return null; - } - - // Also grab the main runspace handle if this is for a ReadLine pipeline and the runspace - // is in process. - if (isReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - GetRunspaceHandleImpl(cancellationToken, isReadLine: false); - } - - return GetRunspaceHandleImpl(cancellationToken, isReadLine); - } - - - /// - /// Get the for the current . - /// - /// - /// The that will be checked prior to - /// completing the returned task. - /// - /// Indicates whether this is for a PSReadLine command. - /// - /// A object representing the asynchronous operation. - /// The property will return the - /// for the current frame. - /// - internal async Task GetRunspaceHandleAsync(CancellationToken cancellationToken, bool isReadLine) - { - if (_isDisposed) - { - return null; - } - - // Also grab the main runspace handle if this is for a ReadLine pipeline and the runspace - // is in process. - if (isReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - await GetRunspaceHandleImplAsync(cancellationToken, isReadLine: false); - } - - return await GetRunspaceHandleImplAsync(cancellationToken, isReadLine); - } - - /// - /// Releases control of the runspace aquired via the . - /// - /// - /// The representing the control to release. - /// - internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) - { - if (_isDisposed) - { - return; - } - - ReleaseRunspaceHandleImpl(runspaceHandle.IsReadLine); - if (runspaceHandle.IsReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - ReleaseRunspaceHandleImpl(isReadLine: false); - } - } - - /// - /// Releases control of the runspace aquired via the . - /// - /// - /// The representing the control to release. - /// - /// - /// A object representing the release of the - /// . - /// - internal async Task ReleaseRunspaceHandleAsync(RunspaceHandle runspaceHandle) - { - if (_isDisposed) - { - return; - } - - await ReleaseRunspaceHandleImplAsync(runspaceHandle.IsReadLine); - if (runspaceHandle.IsReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - await ReleaseRunspaceHandleImplAsync(isReadLine: false); - } - } - - /// - /// Determines if the current frame is unavailable for commands. - /// - /// - /// A value indicating whether the current frame is unavailable for commands. - /// - internal bool IsMainThreadBusy() - { - return !_isDisposed && CurrentFrame.Queue.IsEmpty; - } - - /// - /// Determines if a PSReadLine command is currently running. - /// - /// - /// A value indicating whether a PSReadLine command is currently running. - /// - internal bool IsReadLineBusy() - { - return !_isDisposed && _readLineFrame.Queue.IsEmpty; - } - - /// - /// Blocks until the current frame has been disposed. - /// - /// - /// A delegate that when invoked initates the exit of the current frame. - /// - internal void WaitForCurrentFrameExit(Action initiator) - { - if (_isDisposed) - { - return; - } - - var currentFrame = CurrentFrame; - try - { - initiator.Invoke(currentFrame); - } - finally - { - currentFrame.WaitForFrameExit(CancellationToken.None); - } - } - - /// - /// Blocks until the current frame has been disposed. - /// - internal void WaitForCurrentFrameExit() - { - if (_isDisposed) - { - return; - } - - CurrentFrame.WaitForFrameExit(CancellationToken.None); - } - - /// - /// Blocks until the current frame has been disposed. - /// - /// - /// The used the exit the block prior to - /// the current frame being disposed. - /// - internal void WaitForCurrentFrameExit(CancellationToken cancellationToken) - { - if (_isDisposed) - { - return; - } - - CurrentFrame.WaitForFrameExit(cancellationToken); - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// A delegate that when invoked initates the exit of the current frame. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync(Func initiator) - { - if (_isDisposed) - { - return; - } - - var currentFrame = CurrentFrame; - try - { - await initiator.Invoke(currentFrame); - } - finally - { - await currentFrame.WaitForFrameExitAsync(CancellationToken.None); - } - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// A delegate that when invoked initates the exit of the current frame. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync(Action initiator) - { - if (_isDisposed) - { - return; - } - - var currentFrame = CurrentFrame; - try - { - initiator.Invoke(currentFrame); - } - finally - { - await currentFrame.WaitForFrameExitAsync(CancellationToken.None); - } - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync() - { - if (_isDisposed) - { - return; - } - - await WaitForCurrentFrameExitAsync(CancellationToken.None); - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// The used the exit the block prior to the current frame being disposed. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync(CancellationToken cancellationToken) - { - if (_isDisposed) - { - return; - } - - await CurrentFrame.WaitForFrameExitAsync(cancellationToken); - } - - private AsyncQueue NewHandleQueue() - { - var queue = new AsyncQueue(); - queue.Enqueue(new RunspaceHandle(_powerShellContext)); - return queue; - } - - private RunspaceHandle GetRunspaceHandleImpl(CancellationToken cancellationToken, bool isReadLine) - { - if (isReadLine) - { - return _readLineFrame.Queue.Dequeue(cancellationToken); - } - - return CurrentFrame.Queue.Dequeue(cancellationToken); - } - - private async Task GetRunspaceHandleImplAsync(CancellationToken cancellationToken, bool isReadLine) - { - if (isReadLine) - { - return await _readLineFrame.Queue.DequeueAsync(cancellationToken); - } - - return await CurrentFrame.Queue.DequeueAsync(cancellationToken); - } - - private void ReleaseRunspaceHandleImpl(bool isReadLine) - { - if (isReadLine) - { - _readLineFrame.Queue.Enqueue(new RunspaceHandle(_powerShellContext, true)); - return; - } - - CurrentFrame.Queue.Enqueue(new RunspaceHandle(_powerShellContext, false)); - } - - private async Task ReleaseRunspaceHandleImplAsync(bool isReadLine) - { - if (isReadLine) - { - await _readLineFrame.Queue.EnqueueAsync(new RunspaceHandle(_powerShellContext, true)); - return; - } - - await CurrentFrame.Queue.EnqueueAsync(new RunspaceHandle(_powerShellContext, false)); - } - } -} diff --git a/src/PowerShellEditorServices/Session/PromptNestFrame.cs b/src/PowerShellEditorServices/Session/PromptNestFrame.cs deleted file mode 100644 index cae7dfb8a..000000000 --- a/src/PowerShellEditorServices/Session/PromptNestFrame.cs +++ /dev/null @@ -1,137 +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.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - using System.Management.Automation; - - /// - /// Represents a single frame in the . - /// - internal class PromptNestFrame : IDisposable - { - private const PSInvocationState IndisposableStates = PSInvocationState.Stopping | PSInvocationState.Running; - - private SemaphoreSlim _frameExited = new SemaphoreSlim(initialCount: 0); - - private bool _isDisposed = false; - - /// - /// Gets the instance. - /// - internal PowerShell PowerShell { get; } - - /// - /// Gets the queue that controls command invocation order. - /// - internal AsyncQueue Queue { get; } - - /// - /// Gets the frame type. - /// - internal PromptNestFrameType FrameType { get; } - - /// - /// Gets the . - /// - internal ThreadController ThreadController { get; } - - /// - /// Gets a value indicating whether the frame requires command invocations - /// to be routed to a specific thread. - /// - internal bool IsThreadController { get; } - - internal PromptNestFrame(PowerShell powerShell, AsyncQueue handleQueue) - : this(powerShell, handleQueue, PromptNestFrameType.Normal) - { } - - internal PromptNestFrame( - PowerShell powerShell, - AsyncQueue handleQueue, - PromptNestFrameType frameType) - { - PowerShell = powerShell; - Queue = handleQueue; - FrameType = frameType; - IsThreadController = (frameType & (PromptNestFrameType.Debug | PromptNestFrameType.NestedPrompt)) != 0; - if (!IsThreadController) - { - return; - } - - ThreadController = new ThreadController(this); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - - if (disposing) - { - if (IndisposableStates.HasFlag(PowerShell.InvocationStateInfo.State)) - { - PowerShell.BeginStop( - asyncResult => - { - PowerShell.Runspace = null; - PowerShell.Dispose(); - }, - state: null); - } - else - { - PowerShell.Runspace = null; - PowerShell.Dispose(); - } - - _frameExited.Release(); - } - - _isDisposed = true; - } - - /// - /// Blocks until the frame has been disposed. - /// - /// - /// The that will exit the block when cancelled. - /// - internal void WaitForFrameExit(CancellationToken cancellationToken) - { - _frameExited.Wait(cancellationToken); - _frameExited.Release(); - } - - /// - /// Creates a task object that is completed when the frame has been disposed. - /// - /// - /// The that will be checked prior to completing - /// the returned task. - /// - /// - /// A object that represents this frame being disposed. - /// - internal async Task WaitForFrameExitAsync(CancellationToken cancellationToken) - { - await _frameExited.WaitAsync(cancellationToken); - _frameExited.Release(); - } - } -} diff --git a/src/PowerShellEditorServices/Session/PromptNestFrameType.cs b/src/PowerShellEditorServices/Session/PromptNestFrameType.cs deleted file mode 100644 index b42b42098..000000000 --- a/src/PowerShellEditorServices/Session/PromptNestFrameType.cs +++ /dev/null @@ -1,21 +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.Session -{ - [Flags] - internal enum PromptNestFrameType - { - Normal = 0, - - NestedPrompt = 1, - - Debug = 2, - - Remote = 4 - } -} diff --git a/src/PowerShellEditorServices/Session/RemoteFileManager.cs b/src/PowerShellEditorServices/Session/RemoteFileManager.cs deleted file mode 100644 index c9885b84f..000000000 --- a/src/PowerShellEditorServices/Session/RemoteFileManager.cs +++ /dev/null @@ -1,789 +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 Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Manages files that are accessed from a remote PowerShell session. - /// Also manages the registration and handling of the 'psedit' function. - /// - public class RemoteFileManager - { - #region Fields - - private ILogger logger; - private string remoteFilesPath; - private string processTempPath; - private PowerShellContext powerShellContext; - private IEditorOperations editorOperations; - - private Dictionary filesPerComputer = - new Dictionary(); - - private const string RemoteSessionOpenFile = "PSESRemoteSessionOpenFile"; - - private const string PSEditModule = @"<# - .SYNOPSIS - Opens the specified files in your editor window - .DESCRIPTION - Opens the specified files in your editor window - .EXAMPLE - PS > Open-EditorFile './foo.ps1' - Opens foo.ps1 in your editor - .EXAMPLE - PS > gci ./myDir | Open-EditorFile - Opens everything in 'myDir' in your editor - .INPUTS - Path - an array of files you want to open in your editor - #> - function Open-EditorFile { - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [String[]] - $Path - ) - - begin { - $Paths = @() - } - - process { - $Paths += $Path - } - - end { - if ($Paths.Count -gt 1) { - $preview = $false - } else { - $preview = $true - } - - foreach ($fileName in $Paths) - { - Microsoft.PowerShell.Management\Get-ChildItem $fileName | Where-Object { ! $_.PSIsContainer } | Foreach-Object { - $filePathName = $_.FullName - - # Get file contents - $params = @{ Path=$filePathName; Raw=$true } - if ($PSVersionTable.PSEdition -eq 'Core') - { - $params['AsByteStream']=$true - } - else - { - $params['Encoding']='Byte' - } - - $contentBytes = Microsoft.PowerShell.Management\Get-Content @params - - # Notify client for file open. - Microsoft.PowerShell.Utility\New-Event -SourceIdentifier PSESRemoteSessionOpenFile -EventArguments @($filePathName, $contentBytes, $preview) > $null - } - } - } - } - - <# - .SYNOPSIS - Creates new files and opens them in your editor window - .DESCRIPTION - Creates new files and opens them in your editor window - .EXAMPLE - PS > New-EditorFile './foo.ps1' - Creates and opens a new foo.ps1 in your editor - .EXAMPLE - PS > Get-Process | New-EditorFile proc.txt - Creates and opens a new foo.ps1 in your editor with the contents of the call to Get-Process - .EXAMPLE - PS > Get-Process | New-EditorFile proc.txt -Force - Creates and opens a new foo.ps1 in your editor with the contents of the call to Get-Process. Overwrites the file if it already exists - .INPUTS - Path - an array of files you want to open in your editor - Value - The content you want in the new files - Force - Overwrites a file if it exists - #> - function New-EditorFile { - [CmdletBinding()] - param ( - [Parameter()] - [String[]] - [ValidateNotNullOrEmpty()] - $Path, - - [Parameter(ValueFromPipeline=$true)] - $Value, - - [Parameter()] - [switch] - $Force - ) - - begin { - $valueList = @() - } - - process { - $valueList += $Value - } - - end { - if ($Path) { - foreach ($fileName in $Path) - { - if (-not (Microsoft.PowerShell.Management\Test-Path $fileName) -or $Force) { - $valueList > $fileName - - # Get file contents - $params = @{ Path=$fileName; Raw=$true } - if ($PSVersionTable.PSEdition -eq 'Core') - { - $params['AsByteStream']=$true - } - else - { - $params['Encoding']='Byte' - } - - $contentBytes = Microsoft.PowerShell.Management\Get-Content @params - - if ($Path.Count -gt 1) { - $preview = $false - } else { - $preview = $true - } - - # Notify client for file open. - Microsoft.PowerShell.Utility\New-Event -SourceIdentifier PSESRemoteSessionOpenFile -EventArguments @($fileName, $contentBytes, $preview) > $null - } else { - $PSCmdlet.WriteError( ( - Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @( - [System.Exception]'File already exists.' - $Null - [System.Management.Automation.ErrorCategory]::ResourceExists - $fileName ) ) ) - } - } - } else { - $bytes = [System.Text.Encoding]::UTF8.GetBytes(($valueList | Microsoft.PowerShell.Utility\Out-String)) - Microsoft.PowerShell.Utility\New-Event -SourceIdentifier PSESRemoteSessionOpenFile -EventArguments @($null, $bytes) > $null - } - } - } - - Microsoft.PowerShell.Utility\Set-Alias psedit Open-EditorFile -Scope Global - Microsoft.PowerShell.Core\Export-ModuleMember -Function Open-EditorFile, New-EditorFile - "; - - // This script is templated so that the '-Forward' parameter can be added - // to the script when in non-local sessions - private const string CreatePSEditFunctionScript = @" - param ( - [string] $PSEditModule - ) - - Microsoft.PowerShell.Utility\Register-EngineEvent -SourceIdentifier PSESRemoteSessionOpenFile -Forward -SupportEvent - Microsoft.PowerShell.Core\New-Module -ScriptBlock ([Scriptblock]::Create($PSEditModule)) -Name PSEdit | - Microsoft.PowerShell.Core\Import-Module -Global - "; - - private const string RemovePSEditFunctionScript = @" - Microsoft.PowerShell.Core\Get-Module PSEdit | Microsoft.PowerShell.Core\Remove-Module - - Microsoft.PowerShell.Utility\Unregister-Event -SourceIdentifier PSESRemoteSessionOpenFile -Force -ErrorAction Ignore - "; - - private const string SetRemoteContentsScript = @" - param( - [string] $RemoteFilePath, - [byte[]] $Content - ) - - # Set file contents - $params = @{ Path=$RemoteFilePath; Value=$Content; Force=$true } - if ($PSVersionTable.PSEdition -eq 'Core') - { - $params['AsByteStream']=$true - } - else - { - $params['Encoding']='Byte' - } - - Microsoft.PowerShell.Management\Set-Content @params 2>&1 - "; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the RemoteFileManager class. - /// - /// - /// The PowerShellContext to use for file loading operations. - /// - /// - /// The IEditorOperations instance to use for opening/closing files in the editor. - /// - /// An ILogger implementation used for writing log messages. - public RemoteFileManager( - PowerShellContext powerShellContext, - IEditorOperations editorOperations, - ILogger logger) - { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - this.logger = logger; - this.powerShellContext = powerShellContext; - this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; - - this.editorOperations = editorOperations; - - this.processTempPath = - Path.Combine( - Path.GetTempPath(), - "PSES-" + Process.GetCurrentProcess().Id); - - this.remoteFilesPath = Path.Combine(this.processTempPath, "RemoteFiles"); - - // Delete existing temporary file cache path if it already exists - this.TryDeleteTemporaryPath(); - - // Register the psedit function in the current runspace - this.RegisterPSEditFunction(this.powerShellContext.CurrentRunspace); - } - - #endregion - - #region Public Methods - - /// - /// Opens a remote file, fetching its contents if necessary. - /// - /// - /// The remote file path to be opened. - /// - /// - /// The runspace from which where the remote file will be fetched. - /// - /// - /// The local file path where the remote file's contents have been stored. - /// - public async Task FetchRemoteFileAsync( - string remoteFilePath, - RunspaceDetails runspaceDetails) - { - string localFilePath = null; - - if (!string.IsNullOrEmpty(remoteFilePath)) - { - try - { - RemotePathMappings pathMappings = this.GetPathMappings(runspaceDetails); - localFilePath = this.GetMappedPath(remoteFilePath, runspaceDetails); - - if (!pathMappings.IsRemotePathOpened(remoteFilePath)) - { - // Does the local file already exist? - if (!File.Exists(localFilePath)) - { - // Load the file contents from the remote machine and create the buffer - PSCommand command = new PSCommand(); - command.AddCommand("Microsoft.PowerShell.Management\\Get-Content"); - command.AddParameter("Path", remoteFilePath); - command.AddParameter("Raw"); - command.AddParameter("Encoding", "Byte"); - - byte[] fileContent = - (await this.powerShellContext.ExecuteCommandAsync(command, false, false)) - .FirstOrDefault(); - - if (fileContent != null) - { - this.StoreRemoteFile(localFilePath, fileContent, pathMappings); - } - else - { - this.logger.Write( - LogLevel.Warning, - $"Could not load contents of remote file '{remoteFilePath}'"); - } - } - } - } - catch (IOException e) - { - this.logger.Write( - LogLevel.Error, - $"Caught {e.GetType().Name} while attempting to get remote file at path '{remoteFilePath}'\r\n\r\n{e.ToString()}"); - } - } - - 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 SaveRemoteFileAsync(string localFilePath) - { - string remoteFilePath = - this.GetMappedPath( - localFilePath, - this.powerShellContext.CurrentRunspace); - - this.logger.Write( - LogLevel.Verbose, - $"Saving remote file {remoteFilePath} (local path: {localFilePath})"); - - byte[] localFileContents = null; - try - { - localFileContents = File.ReadAllBytes(localFilePath); - } - catch (IOException e) - { - this.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.ExecuteCommandAsync( - saveCommand, - errorMessages, - false, - false); - - if (errorMessages.Length > 0) - { - this.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. - /// - /// - /// The name of the file to be created under the session path. - /// - /// - /// The contents of the file to be created. - /// - /// - /// The runspace for which the temporary file relates. - /// - /// The full temporary path of the file if successful, null otherwise. - public string CreateTemporaryFile(string fileName, string fileContents, RunspaceDetails runspaceDetails) - { - string temporaryFilePath = Path.Combine(this.processTempPath, fileName); - - try - { - File.WriteAllText(temporaryFilePath, fileContents); - - RemotePathMappings pathMappings = this.GetPathMappings(runspaceDetails); - pathMappings.AddOpenedLocalPath(temporaryFilePath); - } - catch (IOException e) - { - this.logger.Write( - LogLevel.Error, - $"Caught {e.GetType().Name} while attempting to write temporary file at path '{temporaryFilePath}'\r\n\r\n{e.ToString()}"); - - temporaryFilePath = null; - } - - return temporaryFilePath; - } - - /// - /// For a remote or local cache path, get the corresponding local or - /// remote file path. - /// - /// - /// The remote or local file path. - /// - /// - /// The runspace from which the remote file was fetched. - /// - /// The mapped file path. - public string GetMappedPath( - string filePath, - RunspaceDetails runspaceDetails) - { - RemotePathMappings remotePathMappings = this.GetPathMappings(runspaceDetails); - return remotePathMappings.GetMappedPath(filePath); - } - - /// - /// Returns true if the given file path is under the remote files - /// path in the temporary folder. - /// - /// The local file path to check. - /// - /// True if the file path is under the temporary remote files path. - /// - public bool IsUnderRemoteTempPath(string filePath) - { - return filePath.StartsWith( - this.remoteFilesPath, - System.StringComparison.CurrentCultureIgnoreCase); - } - - #endregion - - #region Private Methods - - private string StoreRemoteFile( - string remoteFilePath, - byte[] fileContent, - RunspaceDetails runspaceDetails) - { - RemotePathMappings pathMappings = this.GetPathMappings(runspaceDetails); - string localFilePath = pathMappings.GetMappedPath(remoteFilePath); - - this.StoreRemoteFile( - localFilePath, - fileContent, - pathMappings); - - return localFilePath; - } - - private void StoreRemoteFile( - string localFilePath, - byte[] fileContent, - RemotePathMappings pathMappings) - { - File.WriteAllBytes(localFilePath, fileContent); - pathMappings.AddOpenedLocalPath(localFilePath); - } - - private RemotePathMappings GetPathMappings(RunspaceDetails runspaceDetails) - { - RemotePathMappings remotePathMappings = null; - string computerName = runspaceDetails.SessionDetails.ComputerName; - - if (!this.filesPerComputer.TryGetValue(computerName, out remotePathMappings)) - { - remotePathMappings = new RemotePathMappings(runspaceDetails, this); - this.filesPerComputer.Add(computerName, remotePathMappings); - } - - return remotePathMappings; - } - - private async void HandleRunspaceChangedAsync(object sender, RunspaceChangedEventArgs e) - { - if (e.ChangeAction == RunspaceChangeAction.Enter) - { - this.RegisterPSEditFunction(e.NewRunspace); - } - else - { - // Close any remote files that were opened - if (e.PreviousRunspace.Location == RunspaceLocation.Remote && - (e.ChangeAction == RunspaceChangeAction.Shutdown || - !string.Equals( - e.NewRunspace.SessionDetails.ComputerName, - e.PreviousRunspace.SessionDetails.ComputerName, - StringComparison.CurrentCultureIgnoreCase))) - { - RemotePathMappings remotePathMappings; - if (this.filesPerComputer.TryGetValue(e.PreviousRunspace.SessionDetails.ComputerName, out remotePathMappings)) - { - foreach (string remotePath in remotePathMappings.OpenedPaths) - { - await this.editorOperations?.CloseFileAsync(remotePath); - } - } - } - - if (e.PreviousRunspace != null) - { - this.RemovePSEditFunction(e.PreviousRunspace); - } - } - } - - private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) - { - if (string.Equals(RemoteSessionOpenFile, args.SourceIdentifier, StringComparison.CurrentCultureIgnoreCase)) - { - try - { - if (args.SourceArgs.Length >= 1) - { - string localFilePath = string.Empty; - string remoteFilePath = args.SourceArgs[0] as string; - - // Is this a local process runspace? Treat as a local file - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Local) - { - localFilePath = remoteFilePath; - } - else - { - byte[] fileContent = null; - - if (args.SourceArgs.Length >= 2) - { - // Try to cast as a PSObject to get the BaseObject, if not, then try to case as a byte[] - PSObject sourceObj = args.SourceArgs[1] as PSObject; - if (sourceObj != null) - { - fileContent = sourceObj.BaseObject as byte[]; - } - else - { - fileContent = args.SourceArgs[1] as byte[]; - } - } - - // If fileContent is still null after trying to - // unpack the contents, just return an empty byte - // array. - fileContent = fileContent ?? new byte[0]; - - if (remoteFilePath != null) - { - localFilePath = - this.StoreRemoteFile( - remoteFilePath, - fileContent, - this.powerShellContext.CurrentRunspace); - } - else - { - await this.editorOperations?.NewFileAsync(); - EditorContext context = await this.editorOperations?.GetEditorContextAsync(); - context?.CurrentFile.InsertText(Encoding.UTF8.GetString(fileContent, 0, fileContent.Length)); - } - } - - bool preview = true; - if (args.SourceArgs.Length >= 3) - { - bool? previewCheck = args.SourceArgs[2] as bool?; - preview = previewCheck ?? true; - } - - // Open the file in the editor - this.editorOperations?.OpenFileAsync(localFilePath, preview); - } - } - catch (NullReferenceException e) - { - this.logger.WriteException("Could not store null remote file content", e); - } - } - } - - private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) - { - if (runspaceDetails.Location == RunspaceLocation.Remote && - runspaceDetails.Context == RunspaceContext.Original) - { - try - { - runspaceDetails.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; - - PSCommand createCommand = new PSCommand(); - createCommand - .AddScript(CreatePSEditFunctionScript) - .AddParameter("PSEditModule", PSEditModule); - - if (runspaceDetails.Context == RunspaceContext.DebuggedRunspace) - { - this.powerShellContext.ExecuteCommandAsync(createCommand).Wait(); - } - else - { - using (var powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.Runspace = runspaceDetails.Runspace; - powerShell.Commands = createCommand; - powerShell.Invoke(); - } - } - } - catch (RemoteException e) - { - this.logger.WriteException("Could not create psedit function.", e); - } - } - } - - private void RemovePSEditFunction(RunspaceDetails runspaceDetails) - { - if (runspaceDetails.Location == RunspaceLocation.Remote && - runspaceDetails.Context == RunspaceContext.Original) - { - try - { - if (runspaceDetails.Runspace.Events != null) - { - runspaceDetails.Runspace.Events.ReceivedEvents.PSEventReceived -= HandlePSEventReceivedAsync; - } - - if (runspaceDetails.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) - { - using (var powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.Runspace = runspaceDetails.Runspace; - powerShell.Commands.AddScript(RemovePSEditFunctionScript); - powerShell.Invoke(); - } - } - } - catch (Exception e) when (e is RemoteException || e is PSInvalidOperationException) - { - this.logger.WriteException("Could not remove psedit function.", e); - } - } - } - - private void TryDeleteTemporaryPath() - { - try - { - if (Directory.Exists(this.processTempPath)) - { - Directory.Delete(this.processTempPath, true); - } - - Directory.CreateDirectory(this.processTempPath); - } - catch (IOException e) - { - this.logger.WriteException( - $"Could not delete temporary folder for current process: {this.processTempPath}", e); - } - } - - #endregion - - #region Nested Classes - - private class RemotePathMappings - { - private RunspaceDetails runspaceDetails; - private RemoteFileManager remoteFileManager; - private HashSet openedPaths = new HashSet(); - private Dictionary pathMappings = new Dictionary(); - - public IEnumerable OpenedPaths - { - get { return openedPaths; } - } - - public RemotePathMappings( - RunspaceDetails runspaceDetails, - RemoteFileManager remoteFileManager) - { - this.runspaceDetails = runspaceDetails; - this.remoteFileManager = remoteFileManager; - } - - public void AddPathMapping(string remotePath, string localPath) - { - // Add mappings in both directions - this.pathMappings[localPath.ToLower()] = remotePath; - this.pathMappings[remotePath.ToLower()] = localPath; - } - - public void AddOpenedLocalPath(string openedLocalPath) - { - this.openedPaths.Add(openedLocalPath); - } - - public bool IsRemotePathOpened(string remotePath) - { - return this.openedPaths.Contains(remotePath); - } - - public string GetMappedPath(string filePath) - { - string mappedPath = filePath; - - if (!this.pathMappings.TryGetValue(filePath.ToLower(), out mappedPath)) - { - // If the path isn't mapped yet, generate it - if (!filePath.StartsWith(this.remoteFileManager.remoteFilesPath)) - { - mappedPath = - this.MapRemotePathToLocal( - filePath, - runspaceDetails.SessionDetails.ComputerName); - - this.AddPathMapping(filePath, mappedPath); - } - } - - return mappedPath; - } - - private string MapRemotePathToLocal(string remotePath, string connectionString) - { - // The path generated by this code will look something like - // %TEMP%\PSES-[PID]\RemoteFiles\1205823508\computer-name\MyFile.ps1 - // The "path hash" is just the hashed representation of the remote - // file's full path (sans directory) to try and ensure some amount of - // uniqueness across different files on the remote machine. We put - // the "connection string" after the path slug so that it can be used - // as the differentiator string in editors like VS Code when more than - // one tab has the same filename. - - var sessionDir = Directory.CreateDirectory(this.remoteFileManager.remoteFilesPath); - var pathHashDir = - sessionDir.CreateSubdirectory( - Path.GetDirectoryName(remotePath).GetHashCode().ToString()); - - var remoteFileDir = pathHashDir.CreateSubdirectory(connectionString); - - return - Path.Combine( - remoteFileDir.FullName, - Path.GetFileName(remotePath)); - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/RunspaceChangedEventArgs.cs b/src/PowerShellEditorServices/Session/RunspaceChangedEventArgs.cs deleted file mode 100644 index 12dc76ade..000000000 --- a/src/PowerShellEditorServices/Session/RunspaceChangedEventArgs.cs +++ /dev/null @@ -1,69 +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 Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Defines the set of actions that will cause the runspace to be changed. - /// - public enum RunspaceChangeAction - { - /// - /// The runspace change was caused by entering a new session. - /// - Enter, - - /// - /// The runspace change was caused by exiting the current session. - /// - Exit, - - /// - /// The runspace change was caused by shutting down the service. - /// - Shutdown - } - - /// - /// Provides arguments for the PowerShellContext.RunspaceChanged event. - /// - public class RunspaceChangedEventArgs - { - /// - /// Gets the RunspaceChangeAction which caused this event. - /// - public RunspaceChangeAction ChangeAction { get; private set; } - - /// - /// Gets a RunspaceDetails object describing the previous runspace. - /// - public RunspaceDetails PreviousRunspace { get; private set; } - - /// - /// Gets a RunspaceDetails object describing the new runspace. - /// - public RunspaceDetails NewRunspace { get; private set; } - - /// - /// Creates a new instance of the RunspaceChangedEventArgs class. - /// - /// The action which caused the runspace to change. - /// The previously active runspace. - /// The newly active runspace. - public RunspaceChangedEventArgs( - RunspaceChangeAction changeAction, - RunspaceDetails previousRunspace, - RunspaceDetails newRunspace) - { - Validate.IsNotNull(nameof(previousRunspace), previousRunspace); - - this.ChangeAction = changeAction; - this.PreviousRunspace = previousRunspace; - this.NewRunspace = newRunspace; - } - } -} diff --git a/src/PowerShellEditorServices/Session/RunspaceDetails.cs b/src/PowerShellEditorServices/Session/RunspaceDetails.cs deleted file mode 100644 index d016e2fe8..000000000 --- a/src/PowerShellEditorServices/Session/RunspaceDetails.cs +++ /dev/null @@ -1,321 +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 Microsoft.CSharp.RuntimeBinder; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Management.Automation.Runspaces; -using System.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Specifies the possible types of a runspace. - /// - public enum RunspaceLocation - { - /// - /// A runspace on the local machine. - /// - Local, - - /// - /// A runspace on a different machine. - /// - Remote - } - - /// - /// Specifies the context in which the runspace was encountered. - /// - public enum RunspaceContext - { - /// - /// The original runspace in a local or remote session. - /// - Original, - - /// - /// A runspace in a process that was entered with Enter-PSHostProcess. - /// - EnteredProcess, - - /// - /// A runspace that is being debugged with Debug-Runspace. - /// - DebuggedRunspace - } - - /// - /// Provides details about a runspace being used in the current - /// editing session. - /// - public class RunspaceDetails - { - #region Private Fields - - private Dictionary capabilities = - new Dictionary(); - - #endregion - - #region Properties - - /// - /// Gets the Runspace instance for which this class contains details. - /// - internal Runspace Runspace { get; private set; } - - /// - /// Gets the PowerShell version of the new runspace. - /// - public PowerShellVersionDetails PowerShellVersion { get; private set; } - - /// - /// Gets the runspace location, either Local or Remote. - /// - public RunspaceLocation Location { get; private set; } - - /// - /// Gets the context in which the runspace was encountered. - /// - public RunspaceContext Context { get; private set; } - - /// - /// Gets the "connection string" for the runspace, generally the - /// ComputerName for a remote runspace or the ProcessId of an - /// "Attach" runspace. - /// - public string ConnectionString { get; private set; } - - /// - /// Gets the details of the runspace's session at the time this - /// RunspaceDetails object was created. - /// - public SessionDetails SessionDetails { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the RunspaceDetails class. - /// - /// - /// The runspace for which this instance contains details. - /// - /// - /// The SessionDetails for the runspace. - /// - /// - /// The PowerShellVersionDetails of the runspace. - /// - /// - /// The RunspaceLocation of the runspace. - /// - /// - /// The RunspaceContext of the runspace. - /// - /// - /// The connection string of the runspace. - /// - public RunspaceDetails( - Runspace runspace, - SessionDetails sessionDetails, - PowerShellVersionDetails powerShellVersion, - RunspaceLocation runspaceLocation, - RunspaceContext runspaceContext, - string connectionString) - { - this.Runspace = runspace; - this.SessionDetails = sessionDetails; - this.PowerShellVersion = powerShellVersion; - this.Location = runspaceLocation; - this.Context = runspaceContext; - this.ConnectionString = connectionString; - } - - #endregion - - #region Public Methods - - internal void AddCapability(TCapability capability) - where TCapability : IRunspaceCapability - { - this.capabilities.Add(typeof(TCapability), capability); - } - - internal TCapability GetCapability() - where TCapability : IRunspaceCapability - { - TCapability capability = default(TCapability); - this.TryGetCapability(out capability); - return capability; - } - - internal bool TryGetCapability(out TCapability capability) - where TCapability : IRunspaceCapability - { - IRunspaceCapability capabilityAsInterface = default(TCapability); - if (this.capabilities.TryGetValue(typeof(TCapability), out capabilityAsInterface)) - { - capability = (TCapability)capabilityAsInterface; - return true; - } - - capability = default(TCapability); - return false; - } - - internal bool HasCapability() - { - return this.capabilities.ContainsKey(typeof(TCapability)); - } - - /// - /// Creates and populates a new RunspaceDetails instance for the given runspace. - /// - /// - /// The runspace for which details will be gathered. - /// - /// - /// The SessionDetails for the runspace. - /// - /// An ILogger implementation used for writing log messages. - /// A new RunspaceDetails instance. - internal static RunspaceDetails CreateFromRunspace( - Runspace runspace, - SessionDetails sessionDetails, - ILogger logger) - { - Validate.IsNotNull(nameof(runspace), runspace); - Validate.IsNotNull(nameof(sessionDetails), sessionDetails); - - var runspaceLocation = RunspaceLocation.Local; - var runspaceContext = RunspaceContext.Original; - var versionDetails = PowerShellVersionDetails.GetVersionDetails(runspace, logger); - - string connectionString = null; - - if (runspace.ConnectionInfo != null) - { - // Use 'dynamic' to avoid missing NamedPipeRunspaceConnectionInfo - // on PS v3 and v4 - try - { - dynamic connectionInfo = runspace.ConnectionInfo; - if (connectionInfo.ProcessId != null) - { - connectionString = connectionInfo.ProcessId.ToString(); - runspaceContext = RunspaceContext.EnteredProcess; - } - } - catch (RuntimeBinderException) - { - // ProcessId property isn't on the object, move on. - } - - // Grab the $host.name which will tell us if we're in a PSRP session or not - string hostName = - PowerShellContext.ExecuteScriptAndGetItem( - "$Host.Name", - runspace, - defaultValue: string.Empty); - - // hostname is 'ServerRemoteHost' when the user enters a session. - // ex. Enter-PSSession - // Attaching to process currently needs to be marked as a local session - // so we skip this if block if the runspace is from Enter-PSHostProcess - if (hostName.Equals("ServerRemoteHost", StringComparison.Ordinal) - && runspace.OriginalConnectionInfo?.GetType().ToString() != "System.Management.Automation.Runspaces.NamedPipeConnectionInfo") - { - runspaceLocation = RunspaceLocation.Remote; - connectionString = - runspace.ConnectionInfo.ComputerName + - (connectionString != null ? $"-{connectionString}" : string.Empty); - } - } - - return - new RunspaceDetails( - runspace, - sessionDetails, - versionDetails, - runspaceLocation, - runspaceContext, - connectionString); - } - - /// - /// Creates a clone of the given runspace through which another - /// runspace was attached. Sets the IsAttached property of the - /// resulting RunspaceDetails object to true. - /// - /// - /// The RunspaceDetails object which the new object based. - /// - /// - /// The RunspaceContext of the runspace. - /// - /// - /// The SessionDetails for the runspace. - /// - /// - /// A new RunspaceDetails instance for the attached runspace. - /// - public static RunspaceDetails CreateFromContext( - RunspaceDetails runspaceDetails, - RunspaceContext runspaceContext, - SessionDetails sessionDetails) - { - return - new RunspaceDetails( - runspaceDetails.Runspace, - sessionDetails, - runspaceDetails.PowerShellVersion, - runspaceDetails.Location, - runspaceContext, - runspaceDetails.ConnectionString); - } - - /// - /// Creates a new RunspaceDetails object from a remote - /// debugging session. - /// - /// - /// The RunspaceDetails object which the new object based. - /// - /// - /// The RunspaceLocation of the runspace. - /// - /// - /// The RunspaceContext of the runspace. - /// - /// - /// The SessionDetails for the runspace. - /// - /// - /// A new RunspaceDetails instance for the attached runspace. - /// - public static RunspaceDetails CreateFromDebugger( - RunspaceDetails runspaceDetails, - RunspaceLocation runspaceLocation, - RunspaceContext runspaceContext, - SessionDetails sessionDetails) - { - // TODO: Get the PowerShellVersion correctly! - return - new RunspaceDetails( - runspaceDetails.Runspace, - sessionDetails, - runspaceDetails.PowerShellVersion, - runspaceLocation, - runspaceContext, - runspaceDetails.ConnectionString); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Session/RunspaceHandle.cs b/src/PowerShellEditorServices/Session/RunspaceHandle.cs deleted file mode 100644 index 4947eadbe..000000000 --- a/src/PowerShellEditorServices/Session/RunspaceHandle.cs +++ /dev/null @@ -1,60 +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.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides a handle to the runspace that is managed by - /// a PowerShellContext. The holder of this handle. - /// - public class RunspaceHandle : IDisposable - { - private PowerShellContext powerShellContext; - - /// - /// Gets the runspace that is held by this handle. - /// - public Runspace Runspace - { - get - { - return ((IHostSupportsInteractiveSession)this.powerShellContext).Runspace; - } - } - - internal bool IsReadLine { get; } - - /// - /// Initializes a new instance of the RunspaceHandle class using the - /// given runspace. - /// - /// The PowerShellContext instance which manages the runspace. - public RunspaceHandle(PowerShellContext powerShellContext) - : this(powerShellContext, false) - { } - - internal RunspaceHandle(PowerShellContext powerShellContext, bool isReadLine) - { - this.powerShellContext = powerShellContext; - this.IsReadLine = isReadLine; - } - - /// - /// Disposes the RunspaceHandle once the holder is done using it. - /// Causes the handle to be released back to the PowerShellContext. - /// - public void Dispose() - { - // Release the handle and clear the runspace so that - // no further operations can be performed on it. - this.powerShellContext.ReleaseRunspaceHandle(this); - } - } -} - diff --git a/src/PowerShellEditorServices/Session/SessionDetails.cs b/src/PowerShellEditorServices/Session/SessionDetails.cs deleted file mode 100644 index 708e6ec9d..000000000 --- a/src/PowerShellEditorServices/Session/SessionDetails.cs +++ /dev/null @@ -1,64 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Management.Automation; -using System.Collections; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Provides details about the current PowerShell session. - /// - public class SessionDetails - { - /// - /// Gets the process ID of the current process. - /// - public int? ProcessId { get; private set; } - - /// - /// Gets the name of the current computer. - /// - public string ComputerName { get; private set; } - - /// - /// Gets the current PSHost instance ID. - /// - public Guid? InstanceId { get; private set; } - - /// - /// Creates an instance of SessionDetails using the information - /// contained in the PSObject which was obtained using the - /// PSCommand returned by GetDetailsCommand. - /// - /// - public SessionDetails(PSObject detailsObject) - { - Validate.IsNotNull(nameof(detailsObject), detailsObject); - - Hashtable innerHashtable = detailsObject.BaseObject as Hashtable; - - this.ProcessId = (int)innerHashtable["processId"] as int?; - this.ComputerName = innerHashtable["computerName"] as string; - this.InstanceId = innerHashtable["instanceId"] as Guid?; - } - - /// - /// Gets the PSCommand that gathers details from the - /// current session. - /// - /// A PSCommand used to gather session details. - public static PSCommand GetDetailsCommand() - { - PSCommand infoCommand = new PSCommand(); - infoCommand.AddScript( - "@{ 'computerName' = if ([Environment]::MachineName) {[Environment]::MachineName} else {'localhost'}; 'processId' = $PID; 'instanceId' = $host.InstanceId }"); - - return infoCommand; - } - } -} diff --git a/src/PowerShellEditorServices/Session/SessionStateChangedEventArgs.cs b/src/PowerShellEditorServices/Session/SessionStateChangedEventArgs.cs deleted file mode 100644 index 9a0fed0f3..000000000 --- a/src/PowerShellEditorServices/Session/SessionStateChangedEventArgs.cs +++ /dev/null @@ -1,48 +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 -{ - /// - /// Provides details about a change in state of a PowerShellContext. - /// - public class SessionStateChangedEventArgs - { - /// - /// Gets the new state for the session. - /// - public PowerShellContextState NewSessionState { get; private set; } - - /// - /// Gets the execution result of the operation that caused - /// the state change. - /// - public PowerShellExecutionResult ExecutionResult { get; private set; } - - /// - /// Gets the exception that caused a failure state or null otherwise. - /// - public Exception ErrorException { get; private set; } - - /// - /// Creates a new instance of the SessionStateChangedEventArgs class. - /// - /// The new session state. - /// The result of the operation that caused the state change. - /// An exception that describes the failure, if any. - public SessionStateChangedEventArgs( - PowerShellContextState newSessionState, - PowerShellExecutionResult executionResult, - Exception errorException) - { - this.NewSessionState = newSessionState; - this.ExecutionResult = executionResult; - this.ErrorException = errorException; - } - } -} - diff --git a/src/PowerShellEditorServices/Session/ThreadController.cs b/src/PowerShellEditorServices/Session/ThreadController.cs deleted file mode 100644 index 8720e3fa7..000000000 --- a/src/PowerShellEditorServices/Session/ThreadController.cs +++ /dev/null @@ -1,131 +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.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Session -{ - /// - /// Provides the ability to route PowerShell command invocations to a specific thread. - /// - internal class ThreadController - { - private PromptNestFrame _nestFrame; - - internal AsyncQueue PipelineRequestQueue { get; } - - internal TaskCompletionSource FrameExitTask { get; } - - internal int ManagedThreadId { get; } - - internal bool IsPipelineThread { get; } - - /// - /// Initializes an new instance of the ThreadController class. This constructor should only - /// ever been called from the thread it is meant to control. - /// - /// The parent PromptNestFrame object. - internal ThreadController(PromptNestFrame nestFrame) - { - _nestFrame = nestFrame; - PipelineRequestQueue = new AsyncQueue(); - FrameExitTask = new TaskCompletionSource(); - ManagedThreadId = Thread.CurrentThread.ManagedThreadId; - - // If the debugger stop is triggered on a thread with no default runspace we - // shouldn't attempt to route commands to it. - IsPipelineThread = Runspace.DefaultRunspace != null; - } - - /// - /// Determines if the caller is already on the thread that this object maintains. - /// - /// - /// A value indicating if the caller is already on the thread maintained by this object. - /// - internal bool IsCurrentThread() - { - return Thread.CurrentThread.ManagedThreadId == ManagedThreadId; - } - - /// - /// Requests the invocation of a PowerShell command on the thread maintained by this object. - /// - /// The execution request to send. - /// - /// A task object representing the asynchronous operation. The Result property will return - /// the output of the command invocation. - /// - internal async Task> RequestPipelineExecutionAsync( - PipelineExecutionRequest executionRequest) - { - await PipelineRequestQueue.EnqueueAsync(executionRequest); - return await executionRequest.Results; - } - - /// - /// Retrieves the first currently queued execution request. If there are no pending - /// execution requests then the task will be completed when one is requested. - /// - /// - /// A task object representing the asynchronous operation. The Result property will return - /// the retrieved pipeline execution request. - /// - internal async Task TakeExecutionRequestAsync() - { - return await PipelineRequestQueue.DequeueAsync(); - } - - /// - /// Marks the thread to be exited. - /// - /// - /// The resume action for the debugger. If the frame is not a debugger frame this parameter - /// is ignored. - /// - internal void StartThreadExit(DebuggerResumeAction action) - { - StartThreadExit(action, waitForExit: false); - } - - /// - /// Marks the thread to be exited. - /// - /// - /// The resume action for the debugger. If the frame is not a debugger frame this parameter - /// is ignored. - /// - /// - /// Indicates whether the method should block until the exit is completed. - /// - internal void StartThreadExit(DebuggerResumeAction action, bool waitForExit) - { - Task.Run(() => FrameExitTask.TrySetResult(action)); - if (!waitForExit) - { - return; - } - - _nestFrame.WaitForFrameExit(CancellationToken.None); - } - - /// - /// Creates a task object that completes when the thread has be marked for exit. - /// - /// - /// A task object representing the frame receiving a request to exit. The Result property - /// will return the DebuggerResumeAction supplied with the request. - /// - internal async Task Exit() - { - return await FrameExitTask.Task.ConfigureAwait(false); - } - } -} diff --git a/src/PowerShellEditorServices/Symbols/IDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Symbols/IDocumentSymbolProvider.cs deleted file mode 100644 index 02638c5b0..000000000 --- a/src/PowerShellEditorServices/Symbols/IDocumentSymbolProvider.cs +++ /dev/null @@ -1,24 +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.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices.Symbols -{ - /// - /// Specifies the contract for a document symbols provider. - /// - public interface IDocumentSymbolProvider : IFeatureProvider - { - /// - /// Provides a list of symbols for the given document. - /// - /// - /// The document for which SymbolReferences should be provided. - /// - /// An IEnumerable collection of SymbolReferences. - IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile); - } -} diff --git a/src/PowerShellEditorServices/Symbols/IDocumentSymbols.cs b/src/PowerShellEditorServices/Symbols/IDocumentSymbols.cs deleted file mode 100644 index 6c1937627..000000000 --- a/src/PowerShellEditorServices/Symbols/IDocumentSymbols.cs +++ /dev/null @@ -1,31 +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.Collections.Generic; - -namespace Microsoft.PowerShell.EditorServices.Symbols -{ - /// - /// Specifies the contract for an implementation of - /// the IDocumentSymbols component. - /// - public interface IDocumentSymbols - { - /// - /// Gets the collection of IDocumentSymbolsProvider implementations - /// that are registered with this component. - /// - IFeatureProviderCollection Providers { get; } - - /// - /// Provides a list of symbols for the given document. - /// - /// - /// The document for which SymbolReferences should be provided. - /// - /// An IEnumerable collection of SymbolReferences. - IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile); - } -} diff --git a/src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs deleted file mode 100644 index 762cc002b..000000000 --- a/src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs +++ /dev/null @@ -1,223 +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.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices.Symbols -{ - /// - /// Provides an IDocumentSymbolProvider implementation for - /// enumerating test symbols in Pester test (tests.ps1) files. - /// - public class PesterDocumentSymbolProvider : FeatureProviderBase, IDocumentSymbolProvider - { - - IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( - ScriptFile scriptFile) - { - if (!scriptFile.FilePath.EndsWith( - "tests.ps1", - StringComparison.OrdinalIgnoreCase)) - { - return Enumerable.Empty(); - } - - // Find plausible Pester commands - IEnumerable commandAsts = scriptFile.ScriptAst.FindAll(IsNamedCommandWithArguments, true); - - return commandAsts.OfType() - .Where(IsPesterCommand) - .Select(ast => ConvertPesterAstToSymbolReference(scriptFile, ast)); - } - - /// - /// Test if the given Ast is a regular CommandAst with arguments - /// - /// the PowerShell Ast to test - /// true if the Ast represents a PowerShell command with arguments, false otherwise - private static bool IsNamedCommandWithArguments(Ast ast) - { - CommandAst commandAst = ast as CommandAst; - - return commandAst != null && - commandAst.InvocationOperator != TokenKind.Dot && - PesterSymbolReference.GetCommandType(commandAst.GetCommandName()).HasValue && - commandAst.CommandElements.Count >= 2; - } - - /// - /// Test whether the given CommandAst represents a Pester command - /// - /// the CommandAst to test - /// true if the CommandAst represents a Pester command, false otherwise - private static bool IsPesterCommand(CommandAst commandAst) - { - if (commandAst == null) - { - return false; - } - - // Ensure the first word is a Pester keyword - if (!PesterSymbolReference.PesterKeywords.ContainsKey(commandAst.GetCommandName())) - { - return false; - } - - // Ensure that the last argument of the command is a scriptblock - if (!(commandAst.CommandElements[commandAst.CommandElements.Count-1] is ScriptBlockExpressionAst)) - { - return false; - } - - return true; - } - - /// - /// Convert a CommandAst known to represent a Pester command and a reference to the scriptfile - /// it is in into symbol representing a Pester call for code lens - /// - /// the scriptfile the Pester call occurs in - /// the CommandAst representing the Pester call - /// a symbol representing the Pester call containing metadata for CodeLens to use - private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFile scriptFile, CommandAst pesterCommandAst) - { - string testLine = scriptFile.GetLine(pesterCommandAst.Extent.StartLineNumber); - PesterCommandType? commandName = PesterSymbolReference.GetCommandType(pesterCommandAst.GetCommandName()); - if (commandName == null) - { - return null; - } - - // Search for a name for the test - // If the test has more than one argument for names, we set it to null - string testName = null; - bool alreadySawName = false; - for (int i = 1; i < pesterCommandAst.CommandElements.Count; i++) - { - CommandElementAst currentCommandElement = pesterCommandAst.CommandElements[i]; - - // Check for an explicit "-Name" parameter - if (currentCommandElement is CommandParameterAst parameterAst) - { - // Found -Name parameter, move to next element which is the argument for -TestName - i++; - - if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) - { - alreadySawName = true; - } - - continue; - } - - // Otherwise, if an argument is given with no parameter, we assume it's the name - // If we've already seen a name, we set the name to null - if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) - { - alreadySawName = true; - } - } - - return new PesterSymbolReference( - scriptFile, - commandName.Value, - testLine, - testName, - pesterCommandAst.Extent - ); - } - - private static bool TryGetTestNameArgument(CommandElementAst commandElementAst, out string testName) - { - testName = null; - - if (commandElementAst is StringConstantExpressionAst testNameStrAst) - { - testName = testNameStrAst.Value; - return true; - } - - return (commandElementAst is ExpandableStringExpressionAst); - } - } - - /// - /// Defines command types for Pester test blocks. - /// - public enum PesterCommandType - { - /// - /// Identifies a Describe block. - /// - Describe, - - /// - /// Identifies a Context block. - /// - Context, - - /// - /// Identifies an It block. - /// - It - } - - /// - /// Provides a specialization of SymbolReference containing - /// extra information about Pester test symbols. - /// - public class PesterSymbolReference : SymbolReference - { - /// - /// Lookup for Pester keywords we support. Ideally we could extract these from Pester itself - /// - internal static readonly IReadOnlyDictionary PesterKeywords = - Enum.GetValues(typeof(PesterCommandType)) - .Cast() - .ToDictionary(pct => pct.ToString(), pct => pct, StringComparer.OrdinalIgnoreCase); - - private static char[] DefinitionTrimChars = new char[] { ' ', '{' }; - - /// - /// Gets the name of the test - /// - public string TestName { get; private set; } - - /// - /// Gets the test's command type. - /// - public PesterCommandType Command { get; private set; } - - internal PesterSymbolReference( - ScriptFile scriptFile, - PesterCommandType commandType, - string testLine, - string testName, - IScriptExtent scriptExtent) - : base( - SymbolType.Function, - testLine.TrimEnd(DefinitionTrimChars), - scriptExtent, - scriptFile.FilePath, - testLine) - { - this.Command = commandType; - this.TestName = testName; - } - - internal static PesterCommandType? GetCommandType(string commandName) - { - PesterCommandType pesterCommandType; - if (commandName == null || !PesterKeywords.TryGetValue(commandName, out pesterCommandType)) - { - return null; - } - return pesterCommandType; - } - } -} diff --git a/src/PowerShellEditorServices/Symbols/PsdDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Symbols/PsdDocumentSymbolProvider.cs deleted file mode 100644 index e6e0c4a32..000000000 --- a/src/PowerShellEditorServices/Symbols/PsdDocumentSymbolProvider.cs +++ /dev/null @@ -1,33 +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.Linq; - -namespace Microsoft.PowerShell.EditorServices.Symbols -{ - /// - /// Provides an IDocumentSymbolProvider implementation for - /// enumerating symbols in .psd1 files. - /// - public class PsdDocumentSymbolProvider : FeatureProviderBase, IDocumentSymbolProvider - { - IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( - ScriptFile scriptFile) - { - if ((scriptFile.FilePath != null && - scriptFile.FilePath.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) || - AstOperations.IsPowerShellDataFileAst(scriptFile.ScriptAst)) - { - var findHashtableSymbolsVisitor = new FindHashtableSymbolsVisitor(); - scriptFile.ScriptAst.Visit(findHashtableSymbolsVisitor); - return findHashtableSymbolsVisitor.SymbolReferences; - } - - return Enumerable.Empty(); - } - } -} diff --git a/src/PowerShellEditorServices/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Symbols/ScriptDocumentSymbolProvider.cs deleted file mode 100644 index 9405bf479..000000000 --- a/src/PowerShellEditorServices/Symbols/ScriptDocumentSymbolProvider.cs +++ /dev/null @@ -1,47 +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.Linq; - -namespace Microsoft.PowerShell.EditorServices.Symbols -{ - /// - /// Provides an IDocumentSymbolProvider implementation for - /// enumerating symbols in script (.psd1, .psm1) files. - /// - public class ScriptDocumentSymbolProvider : FeatureProviderBase, IDocumentSymbolProvider - { - private Version powerShellVersion; - - /// - /// Creates an instance of the ScriptDocumentSymbolProvider to - /// target the specified PowerShell version. - /// - /// The target PowerShell version. - public ScriptDocumentSymbolProvider(Version powerShellVersion) - { - this.powerShellVersion = powerShellVersion; - } - - IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( - ScriptFile scriptFile) - { - if (scriptFile != null && - scriptFile.FilePath != null && - (scriptFile.FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || - scriptFile.FilePath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase))) - { - return - AstOperations.FindSymbolsInDocument( - scriptFile.ScriptAst, - this.powerShellVersion); - } - - return Enumerable.Empty(); - } - } -} diff --git a/src/PowerShellEditorServices/Templates/TemplateDetails.cs b/src/PowerShellEditorServices/Templates/TemplateDetails.cs deleted file mode 100644 index c15d58e29..000000000 --- a/src/PowerShellEditorServices/Templates/TemplateDetails.cs +++ /dev/null @@ -1,43 +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.Templates -{ - /// - /// Provides details about a file or project template. - /// - public class TemplateDetails - { - /// - /// Gets or sets the title of the template. - /// - public string Title { get; set; } - - /// - /// Gets or sets the author of the template. - /// - public string Author { get; set; } - - /// - /// Gets or sets the version of the template. - /// - public string Version { get; set; } - - /// - /// Gets or sets the description of the template. - /// - public string Description { get; set; } - - /// - /// Gets or sets the template's comma-delimited string of tags. - /// - public string Tags { get; set; } - - /// - /// Gets or sets the template's folder path. - /// - public string TemplatePath { get; set; } - } -} diff --git a/src/PowerShellEditorServices/Templates/TemplateService.cs b/src/PowerShellEditorServices/Templates/TemplateService.cs deleted file mode 100644 index 18f58085c..000000000 --- a/src/PowerShellEditorServices/Templates/TemplateService.cs +++ /dev/null @@ -1,217 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Linq; -using System.Management.Automation; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Templates -{ - /// - /// Provides a service for listing PowerShell project templates and creating - /// new projects from those templates. This service leverages the Plaster - /// module for creating projects from templates. - /// - public class TemplateService - { - #region Private Fields - - private ILogger logger; - private bool isPlasterLoaded; - private bool? isPlasterInstalled; - private PowerShellContext powerShellContext; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the TemplateService class. - /// - /// The PowerShellContext to use for this service. - /// An ILogger implementation used for writing log messages. - public TemplateService(PowerShellContext powerShellContext, ILogger logger) - { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - this.logger = logger; - this.powerShellContext = powerShellContext; - } - - #endregion - - #region Public Methods - - /// - /// Checks if Plaster is installed on the user's machine. - /// - /// A Task that can be awaited until the check is complete. The result will be true if Plaster is installed. - public async Task ImportPlasterIfInstalledAsync() - { - if (!this.isPlasterInstalled.HasValue) - { - PSCommand psCommand = new PSCommand(); - - psCommand - .AddCommand("Get-Module") - .AddParameter("ListAvailable") - .AddParameter("Name", "Plaster"); - - psCommand - .AddCommand("Sort-Object") - .AddParameter("Descending") - .AddParameter("Property", "Version"); - - psCommand - .AddCommand("Select-Object") - .AddParameter("First", 1); - - this.logger.Write(LogLevel.Verbose, "Checking if Plaster is installed..."); - - var getResult = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, false, false); - - PSObject moduleObject = getResult.First(); - this.isPlasterInstalled = moduleObject != null; - string installedQualifier = - this.isPlasterInstalled.Value - ? string.Empty : "not "; - - this.logger.Write( - LogLevel.Verbose, - $"Plaster is {installedQualifier}installed!"); - - // Attempt to load plaster - if (this.isPlasterInstalled.Value && this.isPlasterLoaded == false) - { - this.logger.Write(LogLevel.Verbose, "Loading Plaster..."); - - psCommand = new PSCommand(); - psCommand - .AddCommand("Import-Module") - .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject) - .AddParameter("PassThru"); - - var importResult = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, false, false); - - this.isPlasterLoaded = importResult.Any(); - string loadedQualifier = - this.isPlasterInstalled.Value - ? "was" : "could not be"; - - this.logger.Write( - LogLevel.Verbose, - $"Plaster {loadedQualifier} loaded successfully!"); - } - } - - return this.isPlasterInstalled.Value; - } - - /// - /// Gets the available file or project templates on the user's - /// machine. - /// - /// - /// If true, searches the user's installed PowerShell modules for - /// included templates. - /// - /// A Task which can be awaited for the TemplateDetails list to be returned. - public async Task GetAvailableTemplatesAsync( - bool includeInstalledModules) - { - if (!this.isPlasterLoaded) - { - throw new InvalidOperationException("Plaster is not loaded, templates cannot be accessed."); - }; - - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand("Get-PlasterTemplate"); - - if (includeInstalledModules) - { - psCommand.AddParameter("IncludeModules"); - } - - var templateObjects = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, false, false); - - this.logger.Write( - LogLevel.Verbose, - $"Found {templateObjects.Count()} Plaster templates"); - - return - templateObjects - .Select(CreateTemplateDetails) - .ToArray(); - } - - /// - /// Creates a new file or project from a specified template and - /// places it in the destination path. This ultimately calls - /// Invoke-Plaster in PowerShell. - /// - /// The folder path containing the template. - /// The folder path where the files will be created. - /// A boolean-returning Task which communicates success or failure. - public async Task CreateFromTemplateAsync( - string templatePath, - string destinationPath) - { - this.logger.Write( - LogLevel.Verbose, - $"Invoking Plaster...\n\n TemplatePath: {templatePath}\n DestinationPath: {destinationPath}"); - - PSCommand command = new PSCommand(); - command.AddCommand("Invoke-Plaster"); - command.AddParameter("TemplatePath", templatePath); - command.AddParameter("DestinationPath", destinationPath); - - var errorString = new System.Text.StringBuilder(); - await this.powerShellContext.ExecuteCommandAsync( - command, - errorString, - new ExecutionOptions - { - WriteOutputToHost = false, - WriteErrorsToHost = true, - InterruptCommandPrompt = true - }); - - // If any errors were written out, creation was not successful - return errorString.Length == 0; - } - - #endregion - - #region Private Methods - - private static TemplateDetails CreateTemplateDetails(PSObject psObject) - { - object[] tags = psObject.Members["Tags"].Value as object[]; - - return new TemplateDetails - { - Title = psObject.Members["Title"].Value as string, - Author = psObject.Members["Author"].Value as string, - Version = psObject.Members["Version"].Value.ToString(), - Description = psObject.Members["Description"].Value as string, - TemplatePath = psObject.Members["TemplatePath"].Value as string, - Tags = - tags != null - ? string.Join(", ", tags) - : string.Empty - }; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Utility/AsyncContext.cs b/src/PowerShellEditorServices/Utility/AsyncContext.cs deleted file mode 100644 index ece383173..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncContext.cs +++ /dev/null @@ -1,53 +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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Simplifies the setup of a SynchronizationContext for the use - /// of async calls in the current thread. - /// - public static class AsyncContext - { - /// - /// Starts a new ThreadSynchronizationContext, attaches it to - /// the thread, and then runs the given async main function. - /// - /// - /// The Task-returning Func which represents the "main" function - /// for the thread. - /// - /// An ILogger implementation used for writing log messages. - public static void Start(Func asyncMainFunc, ILogger logger) - { - // Is there already a synchronization context? - if (SynchronizationContext.Current != null) - { - throw new InvalidOperationException( - "A SynchronizationContext is already assigned on this thread."); - } - - // Create and register a synchronization context for this thread - var threadSyncContext = new ThreadSynchronizationContext(logger); - SynchronizationContext.SetSynchronizationContext(threadSyncContext); - - // Get the main task and act on its completion - Task asyncMainTask = asyncMainFunc(); - asyncMainTask.ContinueWith( - t => threadSyncContext.EndLoop(), - TaskScheduler.Default); - - // Start the synchronization context's request loop and - // wait for the main task to complete - threadSyncContext.RunLoopOnCurrentThread(); - asyncMainTask.GetAwaiter().GetResult(); - } - } -} - diff --git a/src/PowerShellEditorServices/Utility/AsyncContextThread.cs b/src/PowerShellEditorServices/Utility/AsyncContextThread.cs deleted file mode 100644 index c5ca20039..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncContextThread.cs +++ /dev/null @@ -1,86 +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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides a simplified interface for creating a new thread - /// and establishing an AsyncContext in it. - /// - public class AsyncContextThread - { - #region Private Fields - - private Task threadTask; - private string threadName; - private CancellationTokenSource threadCancellationToken = - new CancellationTokenSource(); - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the AsyncContextThread class. - /// - /// - /// The name of the thread for debugging purposes. - /// - public AsyncContextThread(string threadName) - { - this.threadName = threadName; - } - - #endregion - - #region Public Methods - - /// - /// Runs a task on the AsyncContextThread. - /// - /// - /// A Func which returns the task to be run on the thread. - /// - /// An ILogger implementation used for writing log messages. - /// - /// A Task which can be used to monitor the thread for completion. - /// - public Task Run(Func taskReturningFunc, ILogger logger) - { - // Start up a long-running task with the action as the - // main entry point for the thread - this.threadTask = - Task.Factory.StartNew( - () => - { - // Set the thread's name to help with debugging - Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName; - - // Set up an AsyncContext to run the task - AsyncContext.Start(taskReturningFunc, logger); - }, - this.threadCancellationToken.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); - - return this.threadTask; - } - - /// - /// Stops the thread task. - /// - public void Stop() - { - this.threadCancellationToken.Cancel(); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Utility/AsyncDebouncer.cs b/src/PowerShellEditorServices/Utility/AsyncDebouncer.cs deleted file mode 100644 index 9644eff77..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncDebouncer.cs +++ /dev/null @@ -1,169 +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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Restricts the invocation of an operation to a specified time - /// interval. Can also cause previous requests to be cancelled - /// by new requests within that time window. Typically used for - /// buffering information for an operation or ensuring that an - /// operation only runs after some interval. - /// - /// The argument type for the Invoke method. - public abstract class AsyncDebouncer - { - #region Private Fields - - private int flushInterval; - private bool restartOnInvoke; - - private Task currentTimerTask; - private CancellationTokenSource timerCancellationSource; - - private AsyncLock asyncLock = new AsyncLock(); - - #endregion - - #region Public Methods - - /// - /// Creates a new instance of the AsyncDebouncer class with the - /// specified flush interval. If restartOnInvoke is true, any - /// calls to Invoke will cancel previous calls which have not yet - /// passed the flush interval. - /// - /// - /// A millisecond interval to use for flushing prior Invoke calls. - /// - /// - /// If true, Invoke calls will reset prior calls which haven't passed the flush interval. - /// - public AsyncDebouncer(int flushInterval, bool restartOnInvoke) - { - this.flushInterval = flushInterval; - this.restartOnInvoke = restartOnInvoke; - } - - /// - /// Invokes the debouncer with the given input. The debouncer will - /// wait for the specified interval before calling the Flush method - /// to complete the operation. - /// - /// - /// The argument for this implementation's Invoke method. - /// - /// A Task to be awaited until the Invoke is queued. - public async Task InvokeAsync(TInvokeArgs invokeArgument) - { - using (await this.asyncLock.LockAsync()) - { - // Invoke the implementor - await this.OnInvokeAsync(invokeArgument); - - // If there's no timer, start one - if (this.currentTimerTask == null) - { - this.StartTimer(); - } - else if (this.currentTimerTask != null && this.restartOnInvoke) - { - // Restart the existing timer - if (this.CancelTimer()) - { - this.StartTimer(); - } - } - } - } - - /// - /// Flushes the latest state regardless of the current interval. - /// An AsyncDebouncer MUST NOT invoke its own Flush method otherwise - /// deadlocks could occur. - /// - /// A Task to be awaited until Flush completes. - public async Task FlushAsync() - { - using (await this.asyncLock.LockAsync()) - { - // Cancel the current timer - this.CancelTimer(); - - // Flush the current output - await this.OnFlushAsync(); - } - } - - #endregion - - #region Abstract Methods - - /// - /// Implemented by the subclass to take the argument for the - /// future operation that will be performed by OnFlush. - /// - /// - /// The argument for this implementation's OnInvoke method. - /// - /// A Task to be awaited for the invoke to complete. - protected abstract Task OnInvokeAsync(TInvokeArgs invokeArgument); - - /// - /// Implemented by the subclass to complete the current operation. - /// - /// A Task to be awaited for the operation to complete. - protected abstract Task OnFlushAsync(); - - #endregion - - #region Private Methods - - private void StartTimer() - { - this.timerCancellationSource = new CancellationTokenSource(); - - this.currentTimerTask = - Task.Delay(this.flushInterval, this.timerCancellationSource.Token) - .ContinueWith( - t => - { - if (!t.IsCanceled) - { - return this.FlushAsync(); - } - else - { - return Task.FromResult(true); - } - }); - } - - private bool CancelTimer() - { - if (this.timerCancellationSource != null) - { - // Attempt to cancel the timer task - this.timerCancellationSource.Cancel(); - } - - // Was the task cancelled? - bool wasCancelled = - this.currentTimerTask == null || - this.currentTimerTask.IsCanceled; - - // Clear the current task so that another may be created - this.currentTimerTask = null; - - return wasCancelled; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Utility/AsyncLock.cs b/src/PowerShellEditorServices/Utility/AsyncLock.cs deleted file mode 100644 index 5eba1b24f..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncLock.cs +++ /dev/null @@ -1,128 +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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides a simple wrapper over a SemaphoreSlim to allow - /// synchronization locking inside of async calls. Cannot be - /// used recursively. - /// - public class AsyncLock - { - #region Fields - - private Task lockReleaseTask; - private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1); - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the AsyncLock class. - /// - public AsyncLock() - { - this.lockReleaseTask = - Task.FromResult( - (IDisposable)new LockReleaser(this)); - } - - #endregion - - #region Public Methods - - /// - /// Locks - /// - /// A task which has an IDisposable - public Task LockAsync() - { - return this.LockAsync(CancellationToken.None); - } - - /// - /// Obtains or waits for a lock which can be used to synchronize - /// access to a resource. The wait may be cancelled with the - /// given CancellationToken. - /// - /// - /// A CancellationToken which can be used to cancel the lock. - /// - /// - public Task LockAsync(CancellationToken cancellationToken) - { - Task waitTask = lockSemaphore.WaitAsync(cancellationToken); - - return waitTask.IsCompleted ? - this.lockReleaseTask : - waitTask.ContinueWith( - (t, releaser) => - { - return (IDisposable)releaser; - }, - this.lockReleaseTask.Result, - cancellationToken, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default); - } - - /// - /// Obtains or waits for a lock which can be used to synchronize - /// access to a resource. - /// - /// - public IDisposable Lock() - { - return Lock(CancellationToken.None); - } - - /// - /// Obtains or waits for a lock which can be used to synchronize - /// access to a resource. The wait may be cancelled with the - /// given CancellationToken. - /// - /// - /// A CancellationToken which can be used to cancel the lock. - /// - /// - public IDisposable Lock(CancellationToken cancellationToken) - { - lockSemaphore.Wait(cancellationToken); - return this.lockReleaseTask.Result; - } - - #endregion - - #region Private Classes - - /// - /// Provides an IDisposable wrapper around an AsyncLock so - /// that it can easily be used inside of a 'using' block. - /// - private class LockReleaser : IDisposable - { - private AsyncLock lockToRelease; - - internal LockReleaser(AsyncLock lockToRelease) - { - this.lockToRelease = lockToRelease; - } - - public void Dispose() - { - this.lockToRelease.lockSemaphore.Release(); - } - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Utility/AsyncQueue.cs b/src/PowerShellEditorServices/Utility/AsyncQueue.cs deleted file mode 100644 index 85bbc1592..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncQueue.cs +++ /dev/null @@ -1,224 +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.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides a synchronized queue which can be used from within async - /// operations. This is primarily used for producer/consumer scenarios. - /// - /// The type of item contained in the queue. - public class AsyncQueue - { - #region Private Fields - - private AsyncLock queueLock = new AsyncLock(); - private Queue itemQueue; - private Queue> requestQueue; - - #endregion - - #region Properties - - /// - /// Returns true if the queue is currently empty. - /// - public bool IsEmpty { get; private set; } - - #endregion - - #region Constructors - - /// - /// Initializes an empty instance of the AsyncQueue class. - /// - public AsyncQueue() : this(Enumerable.Empty()) - { - } - - /// - /// Initializes an instance of the AsyncQueue class, pre-populated - /// with the given collection of items. - /// - /// - /// An IEnumerable containing the initial items with which the queue will - /// be populated. - /// - public AsyncQueue(IEnumerable initialItems) - { - this.itemQueue = new Queue(initialItems); - this.requestQueue = new Queue>(); - } - - #endregion - - #region Public Methods - - /// - /// Enqueues an item onto the end of the queue. - /// - /// The item to be added to the queue. - /// - /// A Task which can be awaited until the synchronized enqueue - /// operation completes. - /// - public async Task EnqueueAsync(T item) - { - using (await queueLock.LockAsync()) - { - TaskCompletionSource requestTaskSource = null; - - // Are any requests waiting? - while (this.requestQueue.Count > 0) - { - // Is the next request cancelled already? - requestTaskSource = this.requestQueue.Dequeue(); - if (!requestTaskSource.Task.IsCanceled) - { - // Dispatch the item - requestTaskSource.SetResult(item); - return; - } - } - - // No more requests waiting, queue the item for a later request - this.itemQueue.Enqueue(item); - this.IsEmpty = false; - } - } - - /// - /// Enqueues an item onto the end of the queue. - /// - /// The item to be added to the queue. - public void Enqueue(T item) - { - using (queueLock.Lock()) - { - while (this.requestQueue.Count > 0) - { - var requestTaskSource = this.requestQueue.Dequeue(); - if (requestTaskSource.Task.IsCanceled) - { - continue; - } - - requestTaskSource.SetResult(item); - return; - } - } - - this.itemQueue.Enqueue(item); - this.IsEmpty = false; - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. - /// - /// - /// A Task which can be awaited until a value can be dequeued. - /// - public Task DequeueAsync() - { - return this.DequeueAsync(CancellationToken.None); - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. The wait can be cancelled - /// using the given CancellationToken. - /// - /// - /// A CancellationToken with which a dequeue wait can be cancelled. - /// - /// - /// A Task which can be awaited until a value can be dequeued. - /// - public async Task DequeueAsync(CancellationToken cancellationToken) - { - Task requestTask; - - using (await queueLock.LockAsync(cancellationToken)) - { - if (this.itemQueue.Count > 0) - { - // Items are waiting to be taken so take one immediately - T item = this.itemQueue.Dequeue(); - this.IsEmpty = this.itemQueue.Count == 0; - - return item; - } - else - { - // Queue the request for the next item - var requestTaskSource = new TaskCompletionSource(); - this.requestQueue.Enqueue(requestTaskSource); - - // Register the wait task for cancel notifications - cancellationToken.Register( - () => requestTaskSource.TrySetCanceled()); - - requestTask = requestTaskSource.Task; - } - } - - // Wait for the request task to complete outside of the lock - return await requestTask; - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. - /// - /// - public T Dequeue() - { - return Dequeue(CancellationToken.None); - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. The wait can be cancelled - /// using the given CancellationToken. - /// - /// - /// A CancellationToken with which a dequeue wait can be cancelled. - /// - /// - public T Dequeue(CancellationToken cancellationToken) - { - TaskCompletionSource requestTask; - using (queueLock.Lock(cancellationToken)) - { - if (this.itemQueue.Count > 0) - { - T item = this.itemQueue.Dequeue(); - this.IsEmpty = this.itemQueue.Count == 0; - - return item; - } - - requestTask = new TaskCompletionSource(); - this.requestQueue.Enqueue(requestTask); - - if (cancellationToken.CanBeCanceled) - { - cancellationToken.Register(() => requestTask.TrySetCanceled()); - } - } - - return requestTask.Task.GetAwaiter().GetResult(); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Utility/AsyncUtils.cs b/src/PowerShellEditorServices/Utility/AsyncUtils.cs deleted file mode 100644 index 8da21b942..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncUtils.cs +++ /dev/null @@ -1,25 +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.Threading; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides utility methods for common asynchronous operations. - /// - internal static class AsyncUtils - { - /// - /// Creates a with an handle initial and - /// max count of one. - /// - /// A simple single handle . - internal static SemaphoreSlim CreateSimpleLockingSemaphore() - { - return new SemaphoreSlim(initialCount: 1, maxCount: 1); - } - } -} diff --git a/src/PowerShellEditorServices/Utility/ExecutionTimer.cs b/src/PowerShellEditorServices/Utility/ExecutionTimer.cs deleted file mode 100644 index 2634eb6eb..000000000 --- a/src/PowerShellEditorServices/Utility/ExecutionTimer.cs +++ /dev/null @@ -1,105 +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.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Simple timer to be used with `using` to time executions. - /// - /// - /// An example showing how ExecutionTimer is intended to be used - /// - /// using (ExecutionTimer.Start(logger, "Execution of MyMethod completed.")) - /// { - /// MyMethod(various, arguments); - /// } - /// - /// This will print a message like "Execution of MyMethod completed. [50ms]" to the logs. - /// - public struct ExecutionTimer : IDisposable - { - private static readonly ObjectPool s_stopwatchPool = new ObjectPool(); - - private Stopwatch _stopwatch; - - private readonly ILogger _logger; - - private readonly string _message; - - private readonly string _callerMemberName; - - private readonly string _callerFilePath; - - private readonly int _callerLineNumber; - - /// - /// Create a new execution timer and start it. - /// - /// The logger to log the execution timer message in. - /// The message to prefix the execution time with. - /// The name of the calling method or property. - /// The path to the source file of the caller. - /// The line where the timer is called. - /// A new, started execution timer. - public static ExecutionTimer Start( - ILogger logger, - string message, - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, - [CallerLineNumber] int callerLineNumber = -1) - { - var timer = new ExecutionTimer(logger, message, callerMemberName, callerFilePath, callerLineNumber); - timer._stopwatch.Start(); - return timer; - } - - internal ExecutionTimer( - ILogger logger, - string message, - string callerMemberName, - string callerFilePath, - int callerLineNumber) - { - _logger = logger; - _message = message; - _callerMemberName = callerMemberName; - _callerFilePath = callerFilePath; - _callerLineNumber = callerLineNumber; - _stopwatch = s_stopwatchPool.Rent(); - } - - /// - /// Dispose of the execution timer by stopping the stopwatch and then printing - /// the elapsed time in the logs. - /// - public void Dispose() - { - _stopwatch.Stop(); - - string logMessage = new StringBuilder() - .Append(_message) - .Append(" [") - .Append(_stopwatch.ElapsedMilliseconds) - .Append("ms]") - .ToString(); - - _stopwatch.Reset(); - - s_stopwatchPool.Return(_stopwatch); - - _logger.Write( - LogLevel.Verbose, - logMessage, - callerName: _callerMemberName, - callerSourceFile: _callerFilePath, - callerLineNumber: _callerLineNumber); - } - } -} diff --git a/src/PowerShellEditorServices/Utility/Extensions.cs b/src/PowerShellEditorServices/Utility/Extensions.cs deleted file mode 100644 index 08ffea60c..000000000 --- a/src/PowerShellEditorServices/Utility/Extensions.cs +++ /dev/null @@ -1,154 +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.Linq; -using System.Collections.Generic; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - internal static class ObjectExtensions - { - /// - /// Extension to evaluate an object's ToString() method in an exception safe way. This will - /// extension method will not throw. - /// - /// The object on which to call ToString() - /// The ToString() return value or a suitable error message is that throws. - public static string SafeToString(this object obj) - { - string str; - - try - { - str = obj.ToString(); - } - catch (Exception ex) - { - str = $""; - } - - return str; - } - - /// - /// Get the maximum of the elements from the given enumerable. - /// - /// Type of object for which the enumerable is defined. - /// An enumerable object of type T - /// A comparer for ordering elements of type T. The comparer should handle null values. - /// An object of type T. If the enumerable is empty or has all null elements, then the method returns null. - public static T MaxElement(this IEnumerable elements, Func comparer) where T:class - { - if (elements == null) - { - throw new ArgumentNullException(nameof(elements)); - } - - if (comparer == null) - { - throw new ArgumentNullException(nameof(comparer)); - } - - if (!elements.Any()) - { - return null; - } - - var maxElement = elements.First(); - foreach(var element in elements.Skip(1)) - { - if (element != null && comparer(element, maxElement) > 0) - { - maxElement = element; - } - } - - return maxElement; - } - - /// - /// Get the minimum of the elements from the given enumerable. - /// - /// Type of object for which the enumerable is defined. - /// An enumerable object of type T - /// A comparer for ordering elements of type T. The comparer should handle null values. - /// An object of type T. If the enumerable is empty or has all null elements, then the method returns null. - public static T MinElement(this IEnumerable elements, Func comparer) where T : class - { - return MaxElement(elements, (elementX, elementY) => -1 * comparer(elementX, elementY)); - } - - /// - /// Compare extents with respect to their widths. - /// - /// Width of an extent is defined as the difference between its EndOffset and StartOffest properties. - /// - /// Extent of type IScriptExtent. - /// Extent of type IScriptExtent. - /// 0 if extentX and extentY are equal in width. 1 if width of extent X is greater than that of extent Y. Otherwise, -1. - public static int ExtentWidthComparer(this IScriptExtent extentX, IScriptExtent extentY) - { - - if (extentX == null && extentY == null) - { - return 0; - } - - if (extentX != null && extentY == null) - { - return 1; - } - - if (extentX == null) - { - return -1; - } - - var extentWidthX = extentX.EndOffset - extentX.StartOffset; - var extentWidthY = extentY.EndOffset - extentY.StartOffset; - if (extentWidthX > extentWidthY) - { - return 1; - } - else if (extentWidthX < extentWidthY) - { - return -1; - } - else - { - return 0; - } - } - - /// - /// Check if the given coordinates are wholly contained in the instance's extent. - /// - /// Extent of type IScriptExtent. - /// 1-based line number. - /// 1-based column number - /// True if the coordinates are wholly contained in the instance's extent, otherwise, false. - public static bool Contains(this IScriptExtent scriptExtent, int line, int column) - { - if (scriptExtent.StartLineNumber > line || scriptExtent.EndLineNumber < line) - { - return false; - } - - if (scriptExtent.StartLineNumber == line) - { - return scriptExtent.StartColumnNumber <= column; - } - - if (scriptExtent.EndLineNumber == line) - { - return scriptExtent.EndColumnNumber >= column; - } - - return true; - } - } -} diff --git a/src/PowerShellEditorServices/Utility/Logging.cs b/src/PowerShellEditorServices/Utility/Logging.cs deleted file mode 100644 index 4813789d3..000000000 --- a/src/PowerShellEditorServices/Utility/Logging.cs +++ /dev/null @@ -1,288 +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 Serilog; -using Serilog.Events; -using Serilog.Sinks.File; -using Serilog.Sinks.Async; -using System.Runtime.CompilerServices; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Defines the level indicators for log messages. - /// - public enum LogLevel - { - /// - /// Indicates a diagnostic log message. - /// - Diagnostic, - - /// - /// Indicates a verbose log message. - /// - Verbose, - - /// - /// Indicates a normal, non-verbose log message. - /// - Normal, - - /// - /// Indicates a warning message. - /// - Warning, - - /// - /// Indicates an error message. - /// - Error - } - - /// - /// Provides logging for EditorServices - /// - public interface ILogger : IDisposable - { - /// - /// The minimum log level that this logger instance is configured to log at. - /// - LogLevel MinimumConfiguredLogLevel { get; } - - /// - /// Write a message with the given severity to the logs. - /// - /// The severity level of the log message. - /// The log message itself. - /// The name of the calling method. - /// The name of the source file of the caller. - /// The line number where the log is being called. - void Write( - LogLevel logLevel, - string logMessage, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0); - - /// - /// Log an exception in the logs. - /// - /// The error message of the exception to be logged. - /// The exception itself that has been thrown. - /// The name of the method in which the ILogger is being called. - /// The name of the source file in which the ILogger is being called. - /// The line number in the file where the ILogger is being called. - void WriteException( - string errorMessage, - Exception exception, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0); - - /// - /// Log an exception that has been handled cleanly or is otherwise not expected to cause problems in the logs. - /// - /// The error message of the exception to be logged. - /// The exception itself that has been thrown. - /// The name of the method in which the ILogger is being called. - /// The name of the source file in which the ILogger is being called. - /// The line number in the file where the ILogger is being called. - void WriteHandledException( - string errorMessage, - Exception exception, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0); - } - - - /// - /// Manages logging and logger constructor for EditorServices. - /// - public static class Logging - { - /// - /// Builder class for configuring and creating logger instances. - /// - public class Builder - { - /// - /// The level at which to log. - /// - private LogLevel _logLevel; - - /// - /// Paths at which to create log files. - /// - private Dictionary _filePaths; - - /// - /// Whether or not to send logging to the console. - /// - private bool _useConsole; - - /// - /// The log level to use when logging to the console. - /// - private LogLevel? _consoleLogLevel; - - /// - /// Constructs An ILogger implementation builder instance with default configurations: - /// No log files, not logging to console, log level normal. - /// - public Builder() - { - _logLevel = Utility.LogLevel.Normal; - _filePaths = new Dictionary(); - _useConsole = false; - } - - /// - /// The severity level of the messages to log. Not setting this makes the log level default to "Normal". - /// - /// The severity level of the messages to log. - /// the ILogger builder for reuse. - public Builder LogLevel(LogLevel logLevel) - { - _logLevel = logLevel; - return this; - } - - /// - /// Add a path to output a log file to. - /// - /// The path ofethe file to log to. - /// - /// The minimum log level for this file, null defaults to the configured global level. - /// Note that setting a more verbose level than the global configuration won't work -- - /// messages are filtered by the global configuration before they hit file-specific filters. - /// - /// the ILogger builder for reuse. - public Builder AddLogFile(string filePath, LogLevel? logLevel = null) - { - _filePaths.Add(filePath, logLevel); - return this; - } - - /// - /// Configure the ILogger to send log messages to the console. - /// - /// The minimum log level for console logging. - /// the ILogger builder for reuse. - public Builder AddConsoleLogging(LogLevel? logLevel = null) - { - _useConsole = true; - _consoleLogLevel = logLevel; - return this; - } - - /// - /// Take the log configuration and use it to create An ILogger implementation. - /// - /// The constructed logger. - public ILogger Build() - { - var configuration = new LoggerConfiguration() - .MinimumLevel.Is(ConvertLogLevel(_logLevel)); - - if (_useConsole) - { - configuration = configuration.WriteTo.Console( - restrictedToMinimumLevel: ConvertLogLevel(_consoleLogLevel ?? _logLevel), - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Message}{NewLine}"); - } - - foreach (KeyValuePair logFile in _filePaths) - { - configuration = configuration.WriteTo.Async(a => a.File(logFile.Key, - restrictedToMinimumLevel: ConvertLogLevel(logFile.Value ?? _logLevel), - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Message}{NewLine}") - ); - } - - return new PsesLogger(configuration.CreateLogger(), _logLevel); - } - } - - /// - /// A do-nothing logger that simply discards messages. - /// - public static ILogger NullLogger - { - get - { - return s_nullLogger ?? (s_nullLogger = CreateLogger().Build()); - } - } - - private static ILogger s_nullLogger; - - /// - /// Contruct An ILogger implementation with the applied configuration. - /// - /// The constructed logger. - public static Builder CreateLogger() - { - return new Builder(); - } - - /// - /// Convert an EditorServices log level to a Serilog log level. - /// - /// The EditorServices log level. - /// The Serilog LogEventLevel corresponding to the EditorServices log level. - private static LogEventLevel ConvertLogLevel(LogLevel logLevel) - { - switch (logLevel) - { - case LogLevel.Diagnostic: - return LogEventLevel.Verbose; - - case LogLevel.Verbose: - return LogEventLevel.Debug; - - case LogLevel.Normal: - return LogEventLevel.Information; - - case LogLevel.Warning: - return LogEventLevel.Warning; - - case LogLevel.Error: - return LogEventLevel.Error; - } - - throw new ArgumentException($"Unknown LogLevel: '{logLevel}')", nameof(logLevel)); - } - } - - /// - /// Extension method class for the ILogger class. - /// - public static class ILoggerExtensions - { - /// - /// Log the amount of time an execution takes. The intended usage is to call this method in the - /// header of a `using` block to time the interior of the block. - /// - /// The ILogger to log the execution time with. - /// The message to log about what has been executed. - /// The name of the member calling this method. - /// The file where this method has been called. - /// The line number where this method has been called. - /// - public static ExecutionTimer LogExecutionTime( - this ILogger logger, - string message, - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, - [CallerLineNumber] int callerLineNumber = -1) - { - return ExecutionTimer.Start(logger, message, callerMemberName, callerFilePath, callerLineNumber); - } - } -} diff --git a/src/PowerShellEditorServices/Utility/ObjectPool.cs b/src/PowerShellEditorServices/Utility/ObjectPool.cs deleted file mode 100644 index 1a372f02e..000000000 --- a/src/PowerShellEditorServices/Utility/ObjectPool.cs +++ /dev/null @@ -1,29 +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.Collections.Concurrent; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// A basic implementation of the object pool pattern. - /// - internal class ObjectPool - where T : new() - { - private ConcurrentBag _pool = new ConcurrentBag(); - - /// - /// Get an instance of an object, either new or from the pool depending on availability. - /// - public T Rent() => _pool.TryTake(out var obj) ? obj : new T(); - - /// - /// Return an object to the pool. - /// - /// The object to return to the pool. - public void Return(T obj) => _pool.Add(obj); - } -} diff --git a/src/PowerShellEditorServices/Utility/PathUtils.cs b/src/PowerShellEditorServices/Utility/PathUtils.cs deleted file mode 100644 index 6b62122ec..000000000 --- a/src/PowerShellEditorServices/Utility/PathUtils.cs +++ /dev/null @@ -1,49 +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.IO; -using System.Runtime.InteropServices; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Utility to help handling paths across different platforms. - /// - /// - /// Some constants were copied from the internal System.Management.Automation.StringLiterals class. - /// - internal static class PathUtils - { - /// - /// The default path separator used by the base implementation of the providers. - /// - /// Porting note: IO.Path.DirectorySeparatorChar is correct for all platforms. On Windows, - /// it is '\', and on Linux, it is '/', as expected. - /// - internal static readonly char DefaultPathSeparator = Path.DirectorySeparatorChar; - internal static readonly string DefaultPathSeparatorString = DefaultPathSeparator.ToString(); - - /// - /// The alternate path separator used by the base implementation of the providers. - /// - /// Porting note: we do not use .NET's AlternatePathSeparatorChar here because it correctly - /// states that both the default and alternate are '/' on Linux. However, for PowerShell to - /// be "slash agnostic", we need to use the assumption that a '\' is the alternate path - /// separator on Linux. - /// - internal static readonly char AlternatePathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '/' : '\\'; - internal static readonly string AlternatePathSeparatorString = AlternatePathSeparator.ToString(); - - /// - /// Converts all alternate path separators to the current platform's main path separators. - /// - /// The path to normalize. - /// The normalized path. - public static string NormalizePathSeparators(string path) - { - return string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator); - } - } -} diff --git a/src/PowerShellEditorServices/Utility/PsesLogger.cs b/src/PowerShellEditorServices/Utility/PsesLogger.cs deleted file mode 100644 index de257f556..000000000 --- a/src/PowerShellEditorServices/Utility/PsesLogger.cs +++ /dev/null @@ -1,221 +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.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using Serilog.Core; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// An ILogger implementation object for EditorServices, acts as an adapter to Serilog. - /// - public class PsesLogger : ILogger - { - /// - /// The standard log template for all log entries. - /// - private static readonly string s_logMessageTemplate = - "[{LogLevelName:l}] tid:{ThreadId} in '{CallerName:l}' {CallerSourceFile:l}: line {CallerLineNumber}{IndentedLogMsg:l}"; - - /// - /// The name of the ERROR log level. - /// - private static readonly string ErrorLevelName = LogLevel.Error.ToString().ToUpper(); - - /// - /// The name of the WARNING log level. - /// - private static readonly string WarningLevelName = LogLevel.Warning.ToString().ToUpper(); - - /// - /// The internal Serilog logger to log to. - /// - private readonly Logger _logger; - - /// - /// Construct a new logger around a Serilog ILogger. - /// - /// The Serilog logger to use internally. - /// The minimum severity level the logger is configured to log messages at. - internal PsesLogger(Logger logger, LogLevel minimumLogLevel) - { - _logger = logger; - MinimumConfiguredLogLevel = minimumLogLevel; - } - - /// - /// The minimum log level that this logger is configured to log at. - /// - public LogLevel MinimumConfiguredLogLevel { get; } - - /// - /// Write a message with the given severity to the logs. - /// - /// The severity level of the log message. - /// The log message itself. - /// The name of the calling method. - /// The name of the source file of the caller. - /// The line number where the log is being called. - public void Write( - LogLevel logLevel, - string logMessage, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - Write(logLevel, new StringBuilder(logMessage), callerName, callerSourceFile, callerLineNumber); - } - - /// - /// Write a message with the given severity to the logs. Takes a StringBuilder to allow for minimal allocation. - /// - /// The severity level of the log message. - /// The log message itself in StringBuilder form for manipulation. - /// The name of the calling method. - /// The name of the source file of the caller. - /// The line number where the log is being called. - private void Write( - LogLevel logLevel, - StringBuilder logMessage, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - string indentedLogMsg = IndentMsg(logMessage); - string logLevelName = logLevel.ToString().ToUpper(); - - int threadId = Thread.CurrentThread.ManagedThreadId; - - switch (logLevel) - { - case LogLevel.Diagnostic: - _logger.Verbose(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg); - return; - case LogLevel.Verbose: - _logger.Debug(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg); - return; - case LogLevel.Normal: - _logger.Information(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg); - return; - case LogLevel.Warning: - _logger.Warning(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg); - return; - case LogLevel.Error: - _logger.Error(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg); - return; - } - } - - /// - /// Log an exception in the logs. - /// - /// The error message of the exception to be logged. - /// The exception itself that has been thrown. - /// The name of the method in which the ILogger is being called. - /// The name of the source file in which the ILogger is being called. - /// The line number in the file where the ILogger is being called. - public void WriteException( - string errorMessage, - Exception exception, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - StringBuilder body = FormatExceptionMessage("Exception", errorMessage, exception); - Write(LogLevel.Error, body, callerName, callerSourceFile, callerLineNumber); - } - - /// - /// Log an exception that has been handled cleanly or is otherwise not expected to cause problems in the logs. - /// - /// The error message of the exception to be logged. - /// The exception itself that has been thrown. - /// The name of the method in which the ILogger is being called. - /// The name of the source file in which the ILogger is being called. - /// The line number in the file where the ILogger is being called. - public void WriteHandledException( - string errorMessage, - Exception exception, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - StringBuilder body = FormatExceptionMessage("Handled exception", errorMessage, exception); - Write(LogLevel.Warning, body, callerName, callerSourceFile, callerLineNumber); - } - - /// - /// Utility function to indent a log message by one level. - /// - /// Log message string builder to transform. - /// The indented log message string. - private static string IndentMsg(StringBuilder logMessageBuilder) - { - return logMessageBuilder - .Replace(Environment.NewLine, s_indentedPrefix) - .Insert(0, s_indentedPrefix) - .AppendLine() - .ToString(); - } - - /// - /// Creates a prettified log message from an exception. - /// - /// The user-readable tag for this exception entry. - /// The user-readable short description of the error. - /// The exception object itself. Must not be null. - /// An indented, formatted string of the body. - private static StringBuilder FormatExceptionMessage( - string messagePrelude, - string errorMessage, - Exception exception) - { - var sb = new StringBuilder() - .Append(messagePrelude).Append(": ").Append(errorMessage).Append(Environment.NewLine) - .Append(Environment.NewLine) - .Append(exception.ToString()); - - return sb; - } - - /// - /// A newline followed by a single indentation prefix. - /// - private static readonly string s_indentedPrefix = Environment.NewLine + " "; - - #region IDisposable Support - private bool _disposedValue = false; // To detect redundant calls - - /// - /// Internal disposer. - /// - /// Whether or not the object is being disposed. - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - _logger.Dispose(); - } - - _disposedValue = true; - } - } - - /// - /// Dispose of this object, using the Dispose pattern. - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - } - #endregion - } -} diff --git a/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs b/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs deleted file mode 100644 index 14f91e9b5..000000000 --- a/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs +++ /dev/null @@ -1,100 +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.Concurrent; -using System.Threading; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides a SynchronizationContext implementation that can be used - /// in console applications or any thread which doesn't have its - /// own SynchronizationContext. - /// - public class ThreadSynchronizationContext : SynchronizationContext - { - #region Private Fields - - private ILogger logger; - private BlockingCollection> requestQueue = - new BlockingCollection>(); - - #endregion - - #region Constructors - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public ThreadSynchronizationContext(ILogger logger) - { - this.logger = logger; - } - - #endregion - - #region Public Methods - - /// - /// Posts a request for execution to the SynchronizationContext. - /// This will be executed on the SynchronizationContext's thread. - /// - /// - /// The callback to be invoked on the SynchronizationContext's thread. - /// - /// - /// A state object to pass along to the callback when executed through - /// the SynchronizationContext. - /// - public override void Post(SendOrPostCallback callback, object state) - { - if (!this.requestQueue.IsAddingCompleted) - { - // Add the request to the queue - this.requestQueue.Add( - new Tuple( - callback, state)); - } - else - { - this.logger.Write( - LogLevel.Verbose, - "Attempted to post message to synchronization context after it's already completed"); - } - } - - #endregion - - #region Public Methods - - /// - /// Starts the SynchronizationContext message loop on the current thread. - /// - public void RunLoopOnCurrentThread() - { - Tuple request; - - while (this.requestQueue.TryTake(out request, Timeout.Infinite)) - { - // Invoke the request's callback - request.Item1(request.Item2); - } - } - - /// - /// Ends the SynchronizationContext message loop. - /// - public void EndLoop() - { - // Tell the blocking queue that we're done - this.requestQueue.CompleteAdding(); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Utility/Utils.cs b/src/PowerShellEditorServices/Utility/Utils.cs deleted file mode 100644 index 05787be43..000000000 --- a/src/PowerShellEditorServices/Utility/Utils.cs +++ /dev/null @@ -1,21 +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.Runtime.InteropServices; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// General purpose common utilities to prevent reimplementation. - /// - internal static class Utils - { - /// - /// True if we are running on .NET Core, false otherwise. - /// - public static bool IsNetCore { get; } = RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.Ordinal); - } -} diff --git a/src/PowerShellEditorServices/Utility/Validate.cs b/src/PowerShellEditorServices/Utility/Validate.cs deleted file mode 100644 index 19aefe2c7..000000000 --- a/src/PowerShellEditorServices/Utility/Validate.cs +++ /dev/null @@ -1,143 +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; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides common validation methods to simplify method - /// parameter checks. - /// - public static class Validate - { - /// - /// Throws ArgumentNullException if value is null. - /// - /// The name of the parameter being validated. - /// The value of the parameter being validated. - public static void IsNotNull(string parameterName, object valueToCheck) - { - if (valueToCheck == null) - { - throw new ArgumentNullException(parameterName); - } - } - - /// - /// Throws ArgumentOutOfRangeException if the value is outside - /// of the given lower and upper limits. - /// - /// The name of the parameter being validated. - /// The value of the parameter being validated. - /// The lower limit which the value should not be less than. - /// The upper limit which the value should not be greater than. - public static void IsWithinRange( - string parameterName, - int valueToCheck, - int lowerLimit, - int upperLimit) - { - // TODO: Debug assert here if lowerLimit >= upperLimit - - if (valueToCheck < lowerLimit || valueToCheck > upperLimit) - { - throw new ArgumentOutOfRangeException( - parameterName, - valueToCheck, - string.Format( - "Value is not between {0} and {1}", - lowerLimit, - upperLimit)); - } - } - - /// - /// Throws ArgumentOutOfRangeException if the value is greater than or equal - /// to the given upper limit. - /// - /// The name of the parameter being validated. - /// The value of the parameter being validated. - /// The upper limit which the value should be less than. - public static void IsLessThan( - string parameterName, - int valueToCheck, - int upperLimit) - { - if (valueToCheck >= upperLimit) - { - throw new ArgumentOutOfRangeException( - parameterName, - valueToCheck, - string.Format( - "Value is greater than or equal to {0}", - upperLimit)); - } - } - - /// - /// Throws ArgumentOutOfRangeException if the value is less than or equal - /// to the given lower limit. - /// - /// The name of the parameter being validated. - /// The value of the parameter being validated. - /// The lower limit which the value should be greater than. - public static void IsGreaterThan( - string parameterName, - int valueToCheck, - int lowerLimit) - { - if (valueToCheck < lowerLimit) - { - throw new ArgumentOutOfRangeException( - parameterName, - valueToCheck, - string.Format( - "Value is less than or equal to {0}", - lowerLimit)); - } - } - - /// - /// Throws ArgumentException if the value is equal to the undesired value. - /// - /// The type of value to be validated. - /// The name of the parameter being validated. - /// The value that valueToCheck should not equal. - /// The value of the parameter being validated. - public static void IsNotEqual( - string parameterName, - TValue valueToCheck, - TValue undesiredValue) - { - if (EqualityComparer.Default.Equals(valueToCheck, undesiredValue)) - { - throw new ArgumentException( - string.Format( - "The given value '{0}' should not equal '{1}'", - valueToCheck, - undesiredValue), - parameterName); - } - } - - /// - /// Throws ArgumentException if the value is null, an empty string, - /// or a string containing only whitespace. - /// - /// The name of the parameter being validated. - /// The value of the parameter being validated. - public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck) - { - if (string.IsNullOrWhiteSpace(valueToCheck)) - { - throw new ArgumentException( - "Parameter contains a null, empty, or whitespace string.", - parameterName); - } - } - } -} diff --git a/src/PowerShellEditorServices/Workspace/BufferPosition.cs b/src/PowerShellEditorServices/Workspace/BufferPosition.cs deleted file mode 100644 index effdd7660..000000000 --- a/src/PowerShellEditorServices/Workspace/BufferPosition.cs +++ /dev/null @@ -1,111 +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.Diagnostics; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides details about a position in a file buffer. All - /// positions are expressed in 1-based positions (i.e. the - /// first line and column in the file is position 1,1). - /// - [DebuggerDisplay("Position = {Line}:{Column}")] - public class BufferPosition - { - #region Properties - - /// - /// Provides an instance that represents a position that has not been set. - /// - public static readonly BufferPosition None = new BufferPosition(-1, -1); - - /// - /// Gets the line number of the position in the buffer. - /// - public int Line { get; private set; } - - /// - /// Gets the column number of the position in the buffer. - /// - public int Column { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the BufferPosition class. - /// - /// The line number of the position. - /// The column number of the position. - public BufferPosition(int line, int column) - { - this.Line = line; - this.Column = column; - } - - #endregion - - #region Public Methods - - /// - /// Compares two instances of the BufferPosition class. - /// - /// The object to which this instance will be compared. - /// True if the positions are equal, false otherwise. - public override bool Equals(object obj) - { - if (!(obj is BufferPosition)) - { - return false; - } - - BufferPosition other = (BufferPosition)obj; - - return - this.Line == other.Line && - this.Column == other.Column; - } - - /// - /// Calculates a unique hash code that represents this instance. - /// - /// A hash code representing this instance. - public override int GetHashCode() - { - return this.Line.GetHashCode() ^ this.Column.GetHashCode(); - } - - /// - /// Compares two positions to check if one is greater than the other. - /// - /// The first position to compare. - /// The second position to compare. - /// True if positionOne is greater than positionTwo. - public static bool operator >(BufferPosition positionOne, BufferPosition positionTwo) - { - return - (positionOne != null && positionTwo == null) || - (positionOne.Line > positionTwo.Line) || - (positionOne.Line == positionTwo.Line && - positionOne.Column > positionTwo.Column); - } - - /// - /// Compares two positions to check if one is less than the other. - /// - /// The first position to compare. - /// The second position to compare. - /// True if positionOne is less than positionTwo. - public static bool operator <(BufferPosition positionOne, BufferPosition positionTwo) - { - return positionTwo > positionOne; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Workspace/BufferRange.cs b/src/PowerShellEditorServices/Workspace/BufferRange.cs deleted file mode 100644 index 147eed042..000000000 --- a/src/PowerShellEditorServices/Workspace/BufferRange.cs +++ /dev/null @@ -1,123 +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.Diagnostics; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Provides details about a range between two positions in - /// a file buffer. - /// - [DebuggerDisplay("Start = {Start.Line}:{Start.Column}, End = {End.Line}:{End.Column}")] - public class BufferRange - { - #region Properties - - /// - /// Provides an instance that represents a range that has not been set. - /// - public static readonly BufferRange None = new BufferRange(0, 0, 0, 0); - - /// - /// Gets the start position of the range in the buffer. - /// - public BufferPosition Start { get; private set; } - - /// - /// Gets the end position of the range in the buffer. - /// - public BufferPosition End { get; private set; } - - /// - /// Returns true if the current range is non-zero, i.e. - /// contains valid start and end positions. - /// - public bool HasRange - { - get - { - return this.Equals(BufferRange.None); - } - } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the BufferRange class. - /// - /// The start position of the range. - /// The end position of the range. - public BufferRange(BufferPosition start, BufferPosition end) - { - if (start > end) - { - throw new ArgumentException( - string.Format( - "Start position ({0}, {1}) must come before or be equal to the end position ({2}, {3}).", - start.Line, start.Column, - end.Line, end.Column)); - } - - this.Start = start; - this.End = end; - } - - /// - /// Creates a new instance of the BufferRange class. - /// - /// The 1-based starting line number of the range. - /// The 1-based starting column number of the range. - /// The 1-based ending line number of the range. - /// The 1-based ending column number of the range. - public BufferRange( - int startLine, - int startColumn, - int endLine, - int endColumn) - { - this.Start = new BufferPosition(startLine, startColumn); - this.End = new BufferPosition(endLine, endColumn); - } - - #endregion - - #region Public Methods - - /// - /// Compares two instances of the BufferRange class. - /// - /// The object to which this instance will be compared. - /// True if the ranges are equal, false otherwise. - public override bool Equals(object obj) - { - if (!(obj is BufferRange)) - { - return false; - } - - BufferRange other = (BufferRange)obj; - - return - this.Start.Equals(other.Start) && - this.End.Equals(other.End); - } - - /// - /// Calculates a unique hash code that represents this instance. - /// - /// A hash code representing this instance. - public override int GetHashCode() - { - return this.Start.GetHashCode() ^ this.End.GetHashCode(); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Workspace/FileChange.cs b/src/PowerShellEditorServices/Workspace/FileChange.cs deleted file mode 100644 index 79f6925ea..000000000 --- a/src/PowerShellEditorServices/Workspace/FileChange.cs +++ /dev/null @@ -1,45 +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 -{ - /// - /// Contains details relating to a content change in an open file. - /// - public class FileChange - { - /// - /// The string which is to be inserted in the file. - /// - public string InsertString { get; set; } - - /// - /// The 1-based line number where the change starts. - /// - public int Line { get; set; } - - /// - /// The 1-based column offset where the change starts. - /// - public int Offset { get; set; } - - /// - /// The 1-based line number where the change ends. - /// - public int EndLine { get; set; } - - /// - /// The 1-based column offset where the change ends. - /// - public int EndOffset { get; set; } - - /// - /// Indicates that the InsertString is an overwrite - /// of the content, and all stale content and metadata - /// should be discarded. - /// - public bool IsReload { get; set; } - } -} diff --git a/src/PowerShellEditorServices/Workspace/FilePosition.cs b/src/PowerShellEditorServices/Workspace/FilePosition.cs deleted file mode 100644 index a7c9036c7..000000000 --- a/src/PowerShellEditorServices/Workspace/FilePosition.cs +++ /dev/null @@ -1,109 +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 -{ - /// - /// Provides details and operations for a buffer position in a - /// specific file. - /// - public class FilePosition : BufferPosition - { - #region Private Fields - - private ScriptFile scriptFile; - - #endregion - - #region Constructors - - /// - /// Creates a new FilePosition instance for the 1-based line and - /// column numbers in the specified file. - /// - /// The ScriptFile in which the position is located. - /// The 1-based line number in the file. - /// The 1-based column number in the file. - public FilePosition( - ScriptFile scriptFile, - int line, - int column) - : base(line, column) - { - this.scriptFile = scriptFile; - } - - /// - /// Creates a new FilePosition instance for the specified file by - /// copying the specified BufferPosition - /// - /// The ScriptFile in which the position is located. - /// The original BufferPosition from which the line and column will be copied. - public FilePosition( - ScriptFile scriptFile, - BufferPosition copiedPosition) - : this(scriptFile, copiedPosition.Line, copiedPosition.Column) - { - scriptFile.ValidatePosition(copiedPosition); - } - - #endregion - - #region Public Methods - - /// - /// Gets a FilePosition relative to this position by adding the - /// provided line and column offset relative to the contents of - /// the current file. - /// - /// The line offset to add to this position. - /// The column offset to add to this position. - /// A new FilePosition instance for the calculated position. - public FilePosition AddOffset(int lineOffset, int columnOffset) - { - return this.scriptFile.CalculatePosition( - this, - lineOffset, - columnOffset); - } - - /// - /// Gets a FilePosition for the line and column position - /// of the beginning of the current line after any initial - /// whitespace for indentation. - /// - /// A new FilePosition instance for the calculated position. - public FilePosition GetLineStart() - { - string scriptLine = scriptFile.FileLines[this.Line - 1]; - - int lineStartColumn = 1; - for (int i = 0; i < scriptLine.Length; i++) - { - if (!char.IsWhiteSpace(scriptLine[i])) - { - lineStartColumn = i + 1; - break; - } - } - - return new FilePosition(this.scriptFile, this.Line, lineStartColumn); - } - - /// - /// Gets a FilePosition for the line and column position - /// of the end of the current line. - /// - /// A new FilePosition instance for the calculated position. - public FilePosition GetLineEnd() - { - string scriptLine = scriptFile.FileLines[this.Line - 1]; - return new FilePosition(this.scriptFile, this.Line, scriptLine.Length + 1); - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Workspace/ScriptFile.cs b/src/PowerShellEditorServices/Workspace/ScriptFile.cs deleted file mode 100644 index 958778240..000000000 --- a/src/PowerShellEditorServices/Workspace/ScriptFile.cs +++ /dev/null @@ -1,679 +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.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Contains the details and contents of an open script file. - /// - public class ScriptFile - { - #region Private Fields - - private static readonly string[] s_newlines = new[] - { - "\r\n", - "\n" - }; - - private Version powerShellVersion; - - #endregion - - #region Properties - - /// - /// Gets a unique string that identifies this file. At this time, - /// this property returns a normalized version of the value stored - /// in the FilePath property. - /// - public string Id - { - get { return this.FilePath.ToLower(); } - } - - /// - /// Gets the path at which this file resides. - /// - public string FilePath { get; private set; } - - /// - /// Gets the path which the editor client uses to identify this file. - /// - public string ClientFilePath { get; private set; } - - /// - /// Gets the file path in LSP DocumentUri form. The ClientPath property must not be null. - /// - public string DocumentUri - { - get - { - return this.ClientFilePath == null - ? string.Empty - : Workspace.ConvertPathToDocumentUri(this.ClientFilePath); - } - } - - /// - /// Gets or sets a boolean that determines whether - /// semantic analysis should be enabled for this file. - /// For internal use only. - /// - internal bool IsAnalysisEnabled { get; set; } - - /// - /// Gets a boolean that determines whether this file is - /// in-memory or not (either unsaved or non-file content). - /// - public bool IsInMemory { get; private set; } - - /// - /// Gets a string containing the full contents of the file. - /// - public string Contents - { - get - { - return string.Join(Environment.NewLine, this.FileLines); - } - } - - /// - /// Gets a BufferRange that represents the entire content - /// range of the file. - /// - public BufferRange FileRange { get; private set; } - - /// - /// Gets the list of syntax markers found by parsing this - /// file's contents. - /// - public List DiagnosticMarkers - { - get; - private set; - } - - /// - /// Gets the list of strings for each line of the file. - /// - internal List FileLines - { - get; - private set; - } - - /// - /// Gets the ScriptBlockAst representing the parsed script contents. - /// - public ScriptBlockAst ScriptAst - { - get; - private set; - } - - /// - /// Gets the array of Tokens representing the parsed script contents. - /// - public Token[] ScriptTokens - { - get; - private set; - } - - /// - /// Gets the array of filepaths dot sourced in this ScriptFile - /// - public string[] ReferencedFiles - { - get; - private set; - } - - #endregion - - #region Constructors - - /// - /// Creates a new ScriptFile instance by reading file contents from - /// the given TextReader. - /// - /// The path at which the script file resides. - /// The path which the client uses to identify the file. - /// The TextReader to use for reading the file's contents. - /// The version of PowerShell for which the script is being parsed. - public ScriptFile( - string filePath, - string clientFilePath, - TextReader textReader, - Version powerShellVersion) - { - this.FilePath = filePath; - this.ClientFilePath = clientFilePath; - this.IsAnalysisEnabled = true; - this.IsInMemory = Workspace.IsPathInMemory(filePath); - this.powerShellVersion = powerShellVersion; - - // SetFileContents() calls ParseFileContents() which initializes the rest of the properties. - this.SetFileContents(textReader.ReadToEnd()); - } - - /// - /// Creates a new ScriptFile instance with the specified file contents. - /// - /// The path at which the script file resides. - /// The path which the client uses to identify the file. - /// The initial contents of the script file. - /// The version of PowerShell for which the script is being parsed. - public ScriptFile( - string filePath, - string clientFilePath, - string initialBuffer, - Version powerShellVersion) - : this( - filePath, - clientFilePath, - new StringReader(initialBuffer), - powerShellVersion) - { - } - - /// - /// Creates a new ScriptFile instance with the specified filepath. - /// - /// The path at which the script file resides. - /// The path which the client uses to identify the file. - /// The version of PowerShell for which the script is being parsed. - public ScriptFile( - string filePath, - string clientFilePath, - Version powerShellVersion) - : this( - filePath, - clientFilePath, - File.ReadAllText(filePath), - powerShellVersion) - { - } - - #endregion - - #region Public Methods - - /// - /// Get the lines in a string. - /// - /// Input string to be split up into lines. - /// The lines in the string. - [Obsolete("This method is not designed for public exposure and will be retired in later versions of EditorServices")] - public static IList GetLines(string text) - { - return GetLinesInternal(text); - } - - /// - /// Get the lines in a string. - /// - /// Input string to be split up into lines. - /// The lines in the string. - internal static List GetLinesInternal(string text) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - return new List(text.Split(s_newlines, StringSplitOptions.None)); - } - - /// - /// Deterines whether the supplied path indicates the file is an "untitled:Unitled-X" - /// which has not been saved to file. - /// - /// The path to check. - /// True if the path is an untitled file, false otherwise. - public static bool IsUntitledPath(string path) - { - Validate.IsNotNull(nameof(path), path); - - return path.ToLower().StartsWith("untitled:"); - } - - /// - /// Gets a line from the file's contents. - /// - /// The 1-based line number in the file. - /// The complete line at the given line number. - public string GetLine(int lineNumber) - { - Validate.IsWithinRange( - "lineNumber", lineNumber, - 1, this.FileLines.Count + 1); - - return this.FileLines[lineNumber - 1]; - } - - /// - /// Gets a range of lines from the file's contents. - /// - /// The buffer range from which lines will be extracted. - /// An array of strings from the specified range of the file. - public string[] GetLinesInRange(BufferRange bufferRange) - { - this.ValidatePosition(bufferRange.Start); - this.ValidatePosition(bufferRange.End); - - List linesInRange = new List(); - - int startLine = bufferRange.Start.Line, - endLine = bufferRange.End.Line; - - for (int line = startLine; line <= endLine; line++) - { - string currentLine = this.FileLines[line - 1]; - int startColumn = - line == startLine - ? bufferRange.Start.Column - : 1; - int endColumn = - line == endLine - ? bufferRange.End.Column - : currentLine.Length + 1; - - currentLine = - currentLine.Substring( - startColumn - 1, - endColumn - startColumn); - - linesInRange.Add(currentLine); - } - - return linesInRange.ToArray(); - } - - /// - /// Throws ArgumentOutOfRangeException if the given position is outside - /// of the file's buffer extents. - /// - /// The position in the buffer to be validated. - public void ValidatePosition(BufferPosition bufferPosition) - { - this.ValidatePosition( - bufferPosition.Line, - bufferPosition.Column); - } - - /// - /// Throws ArgumentOutOfRangeException if the given position is outside - /// of the file's buffer extents. - /// - /// The 1-based line to be validated. - /// The 1-based column to be validated. - public void ValidatePosition(int line, int column) - { - int maxLine = this.FileLines.Count; - if (line < 1 || line > maxLine) - { - throw new ArgumentOutOfRangeException($"Position {line}:{column} is outside of the line range of 1 to {maxLine}."); - } - - // The maximum column is either **one past** the length of the string - // or 1 if the string is empty. - string lineString = this.FileLines[line - 1]; - int maxColumn = lineString.Length > 0 ? lineString.Length + 1 : 1; - - if (column < 1 || column > maxColumn) - { - throw new ArgumentOutOfRangeException($"Position {line}:{column} is outside of the column range of 1 to {maxColumn}."); - } - } - - - /// - /// Defunct ValidatePosition method call. The isInsertion parameter is ignored. - /// - /// - /// - /// - [Obsolete("Use ValidatePosition(int, int) instead")] - public void ValidatePosition(int line, int column, bool isInsertion) - { - ValidatePosition(line, column); - } - - /// - /// Applies the provided FileChange to the file's contents - /// - /// The FileChange to apply to the file's contents. - public void ApplyChange(FileChange fileChange) - { - // Break up the change lines - string[] changeLines = fileChange.InsertString.Split('\n'); - - if (fileChange.IsReload) - { - this.FileLines.Clear(); - foreach (var changeLine in changeLines) - { - this.FileLines.Add(changeLine); - } - } - else - { - // VSCode sometimes likes to give the change start line as (FileLines.Count + 1). - // This used to crash EditorServices, but we now treat it as an append. - // See https://github.com/PowerShell/vscode-powershell/issues/1283 - if (fileChange.Line == this.FileLines.Count + 1) - { - foreach (string addedLine in changeLines) - { - string finalLine = addedLine.TrimEnd('\r'); - this.FileLines.Add(finalLine); - } - } - // Similarly, when lines are deleted from the end of the file, - // VSCode likes to give the end line as (FileLines.Count + 1). - else if (fileChange.EndLine == this.FileLines.Count + 1 && String.Empty.Equals(fileChange.InsertString)) - { - int lineIndex = fileChange.Line - 1; - this.FileLines.RemoveRange(lineIndex, this.FileLines.Count - lineIndex); - } - // Otherwise, the change needs to go between existing content - else - { - this.ValidatePosition(fileChange.Line, fileChange.Offset); - this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset); - - // Get the first fragment of the first line - string firstLineFragment = - this.FileLines[fileChange.Line - 1] - .Substring(0, fileChange.Offset - 1); - - // Get the last fragment of the last line - string endLine = this.FileLines[fileChange.EndLine - 1]; - string lastLineFragment = - endLine.Substring( - fileChange.EndOffset - 1, - (this.FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1); - - // Remove the old lines - for (int i = 0; i <= fileChange.EndLine - fileChange.Line; i++) - { - this.FileLines.RemoveAt(fileChange.Line - 1); - } - - // Build and insert the new lines - int currentLineNumber = fileChange.Line; - for (int changeIndex = 0; changeIndex < changeLines.Length; changeIndex++) - { - // Since we split the lines above using \n, make sure to - // trim the ending \r's off as well. - string finalLine = changeLines[changeIndex].TrimEnd('\r'); - - // Should we add first or last line fragments? - if (changeIndex == 0) - { - // Append the first line fragment - finalLine = firstLineFragment + finalLine; - } - if (changeIndex == changeLines.Length - 1) - { - // Append the last line fragment - finalLine = finalLine + lastLineFragment; - } - - this.FileLines.Insert(currentLineNumber - 1, finalLine); - currentLineNumber++; - } - } - } - - // Parse the script again to be up-to-date - this.ParseFileContents(); - } - - /// - /// Calculates the zero-based character offset of a given - /// line and column position in the file. - /// - /// The 1-based line number from which the offset is calculated. - /// The 1-based column number from which the offset is calculated. - /// The zero-based offset for the given file position. - public int GetOffsetAtPosition(int lineNumber, int columnNumber) - { - Validate.IsWithinRange("lineNumber", lineNumber, 1, this.FileLines.Count); - Validate.IsGreaterThan("columnNumber", columnNumber, 0); - - int offset = 0; - - for (int i = 0; i < lineNumber; i++) - { - if (i == lineNumber - 1) - { - // Subtract 1 to account for 1-based column numbering - offset += columnNumber - 1; - } - else - { - // Add an offset to account for the current platform's newline characters - offset += this.FileLines[i].Length + Environment.NewLine.Length; - } - } - - return offset; - } - - /// - /// Calculates a FilePosition relative to a starting BufferPosition - /// using the given 1-based line and column offset. - /// - /// The original BufferPosition from which an new position should be calculated. - /// The 1-based line offset added to the original position in this file. - /// The 1-based column offset added to the original position in this file. - /// A new FilePosition instance with the resulting line and column number. - public FilePosition CalculatePosition( - BufferPosition originalPosition, - int lineOffset, - int columnOffset) - { - int newLine = originalPosition.Line + lineOffset, - newColumn = originalPosition.Column + columnOffset; - - this.ValidatePosition(newLine, newColumn); - - string scriptLine = this.FileLines[newLine - 1]; - newColumn = Math.Min(scriptLine.Length + 1, newColumn); - - return new FilePosition(this, newLine, newColumn); - } - - /// - /// Calculates the 1-based line and column number position based - /// on the given buffer offset. - /// - /// The buffer offset to convert. - /// A new BufferPosition containing the position of the offset. - public BufferPosition GetPositionAtOffset(int bufferOffset) - { - BufferRange bufferRange = - GetRangeBetweenOffsets( - bufferOffset, bufferOffset); - - return bufferRange.Start; - } - - /// - /// Calculates the 1-based line and column number range based on - /// the given start and end buffer offsets. - /// - /// The start offset of the range. - /// The end offset of the range. - /// A new BufferRange containing the positions in the offset range. - public BufferRange GetRangeBetweenOffsets(int startOffset, int endOffset) - { - bool foundStart = false; - int currentOffset = 0; - int searchedOffset = startOffset; - - BufferPosition startPosition = new BufferPosition(0, 0); - BufferPosition endPosition = startPosition; - - int line = 0; - while (line < this.FileLines.Count) - { - if (searchedOffset <= currentOffset + this.FileLines[line].Length) - { - int column = searchedOffset - currentOffset; - - // Have we already found the start position? - if (foundStart) - { - // Assign the end position and end the search - endPosition = new BufferPosition(line + 1, column + 1); - break; - } - else - { - startPosition = new BufferPosition(line + 1, column + 1); - - // Do we only need to find the start position? - if (startOffset == endOffset) - { - endPosition = startPosition; - break; - } - else - { - // Since the end offset can be on the same line, - // skip the line increment and continue searching - // for the end position - foundStart = true; - searchedOffset = endOffset; - continue; - } - } - } - - // Increase the current offset and include newline length - currentOffset += this.FileLines[line].Length + Environment.NewLine.Length; - line++; - } - - return new BufferRange(startPosition, endPosition); - } - - #endregion - - #region Private Methods - - private void SetFileContents(string fileContents) - { - // Split the file contents into lines and trim - // any carriage returns from the strings. - this.FileLines = GetLinesInternal(fileContents); - - // Parse the contents to get syntax tree and errors - this.ParseFileContents(); - } - - /// - /// Parses the current file contents to get the AST, tokens, - /// and parse errors. - /// - private void ParseFileContents() - { - ParseError[] parseErrors = null; - - // First, get the updated file range - int lineCount = this.FileLines.Count; - if (lineCount > 0) - { - this.FileRange = - new BufferRange( - new BufferPosition(1, 1), - new BufferPosition( - lineCount + 1, - this.FileLines[lineCount - 1].Length + 1)); - } - else - { - this.FileRange = BufferRange.None; - } - - try - { - Token[] scriptTokens; - - // This overload appeared with Windows 10 Update 1 - if (this.powerShellVersion.Major >= 6 || - (this.powerShellVersion.Major == 5 && this.powerShellVersion.Build >= 10586)) - { - // Include the file path so that module relative - // paths are evaluated correctly - this.ScriptAst = - Parser.ParseInput( - this.Contents, - this.FilePath, - out scriptTokens, - out parseErrors); - } - else - { - this.ScriptAst = - Parser.ParseInput( - this.Contents, - out scriptTokens, - out parseErrors); - } - - this.ScriptTokens = scriptTokens; - } - catch (RuntimeException ex) - { - var parseError = - new ParseError( - null, - ex.ErrorRecord.FullyQualifiedErrorId, - ex.Message); - - parseErrors = new[] { parseError }; - this.ScriptTokens = new Token[0]; - this.ScriptAst = null; - } - - // Translate parse errors into syntax markers - this.DiagnosticMarkers = - parseErrors - .Select(ScriptFileMarker.FromParseError) - .ToList(); - - // Untitled files have no directory - // Discussed in https://github.com/PowerShell/PowerShellEditorServices/pull/815. - // Rather than working hard to enable things for untitled files like a phantom directory, - // users should save the file. - if (IsUntitledPath(this.FilePath)) - { - // Need to initialize the ReferencedFiles property to an empty array. - this.ReferencedFiles = new string[0]; - return; - } - - // Get all dot sourced referenced files and store them - this.ReferencedFiles = AstOperations.FindDotSourcedIncludes(this.ScriptAst, Path.GetDirectoryName(this.FilePath)); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Workspace/ScriptFileMarker.cs b/src/PowerShellEditorServices/Workspace/ScriptFileMarker.cs deleted file mode 100644 index 9eb73cb6c..000000000 --- a/src/PowerShellEditorServices/Workspace/ScriptFileMarker.cs +++ /dev/null @@ -1,189 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Management.Automation; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Contains details for a code correction which can be applied from a ScriptFileMarker. - /// - public class MarkerCorrection - { - /// - /// Gets or sets the display name of the code correction. - /// - public string Name { get; set; } - - /// - /// Gets or sets the list of ScriptRegions that define the edits to be made by the correction. - /// - public ScriptRegion[] Edits { get; set; } - } - - /// - /// Defines the message level of a script file marker. - /// - public enum ScriptFileMarkerLevel - { - ///  -        /// Information: This warning is trivial, but may be useful. They are recommended by PowerShell best practice. -        ///  -        Information = 0, -        ///  -        /// WARNING: This warning may cause a problem or does not follow PowerShell's recommended guidelines. -        ///  -        Warning = 1, -        ///  -        /// ERROR: This warning is likely to cause a problem or does not follow PowerShell's required guidelines. -        ///  -        Error = 2, -        ///  -        /// ERROR: This diagnostic is caused by an actual parsing error, and is generated only by the engine. -        ///  -        ParseError = 3 - }; - - /// - /// Contains details about a marker that should be displayed - /// for the a script file. The marker information could come - /// from syntax parsing or semantic analysis of the script. - /// - public class ScriptFileMarker - { - #region Properties - - /// - /// Gets or sets the marker's message string. - /// - public string Message { get; set; } - - /// - /// Gets or sets the ruleName associated with this marker. - /// - public string RuleName { get; set; } - - /// - /// Gets or sets the marker's message level. - /// - public ScriptFileMarkerLevel Level { get; set; } - - /// - /// Gets or sets the ScriptRegion where the marker should appear. - /// - public ScriptRegion ScriptRegion { get; set; } - - /// - /// Gets or sets an optional code correction that can be applied based on this marker. - /// - public MarkerCorrection Correction { get; set; } - - /// - /// Gets or sets the name of the marker's source like "PowerShell" - /// or "PSScriptAnalyzer". - /// - public string Source { get; set; } - - #endregion - - #region Public Methods - - internal static ScriptFileMarker FromParseError( - ParseError parseError) - { - Validate.IsNotNull("parseError", parseError); - - return new ScriptFileMarker - { - Message = parseError.Message, - Level = ScriptFileMarkerLevel.Error, - ScriptRegion = ScriptRegion.Create(parseError.Extent), - Source = "PowerShell" - }; - } - private static string GetIfExistsString(PSObject psobj, string memberName) - { - if (psobj.Members.Match(memberName).Count > 0) - { - return psobj.Members[memberName].Value != null ? (string)psobj.Members[memberName].Value : ""; - } - else - { - return ""; - } - } - - internal static ScriptFileMarker FromDiagnosticRecord(PSObject psObject) - { - Validate.IsNotNull("psObject", psObject); - MarkerCorrection correction = null; - - // make sure psobject is of type DiagnosticRecord - if (!psObject.TypeNames.Contains( - "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord", - StringComparer.OrdinalIgnoreCase)) - { - throw new ArgumentException("Input PSObject must of DiagnosticRecord type."); - } - - // casting psobject to dynamic allows us to access - // the diagnostic record's properties directly i.e. . - // without having to go through PSObject's Members property. - var diagnosticRecord = psObject as dynamic; - - if (diagnosticRecord.SuggestedCorrections != null) - { - var suggestedCorrections = diagnosticRecord.SuggestedCorrections as dynamic; - List editRegions = new List(); - string correctionMessage = null; - foreach (var suggestedCorrection in suggestedCorrections) - { - editRegions.Add(new ScriptRegion - { - File = diagnosticRecord.ScriptPath, - Text = suggestedCorrection.Text, - StartLineNumber = suggestedCorrection.StartLineNumber, - StartColumnNumber = suggestedCorrection.StartColumnNumber, - EndLineNumber = suggestedCorrection.EndLineNumber, - EndColumnNumber = suggestedCorrection.EndColumnNumber - }); - correctionMessage = suggestedCorrection.Description; - } - - correction = new MarkerCorrection - { - Name = correctionMessage == null ? diagnosticRecord.Message : correctionMessage, - Edits = editRegions.ToArray() - }; - } - - string severity = diagnosticRecord.Severity.ToString(); - if (!Enum.TryParse(severity, out ScriptFileMarkerLevel level)) - { - throw new ArgumentException( - $"The provided DiagnosticSeverity value '{severity}' is unknown.", - "diagnosticSeverity"); - } - - return new ScriptFileMarker - { - Message = $"{diagnosticRecord.Message as string}", - RuleName = $"{diagnosticRecord.RuleName as string}", - Level = level, - ScriptRegion = ScriptRegion.Create(diagnosticRecord.Extent as IScriptExtent), - Correction = correction, - Source = "PSScriptAnalyzer" - }; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Workspace/ScriptRegion.cs b/src/PowerShellEditorServices/Workspace/ScriptRegion.cs deleted file mode 100644 index 5717b1382..000000000 --- a/src/PowerShellEditorServices/Workspace/ScriptRegion.cs +++ /dev/null @@ -1,112 +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.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Contains details about a specific region of text in script file. - /// - public sealed class ScriptRegion : IScriptExtent - { - #region Properties - - /// - /// Gets the file path of the script file in which this region is contained. - /// - public string File { get; set; } - - /// - /// Gets or sets the text that is contained within the region. - /// - public string Text { get; set; } - - /// - /// Gets or sets the starting line number of the region. - /// - public int StartLineNumber { get; set; } - - /// - /// Gets or sets the starting column number of the region. - /// - public int StartColumnNumber { get; set; } - - /// - /// Gets or sets the starting file offset of the region. - /// - public int StartOffset { get; set; } - - /// - /// Gets or sets the ending line number of the region. - /// - public int EndLineNumber { get; set; } - - /// - /// Gets or sets the ending column number of the region. - /// - public int EndColumnNumber { get; set; } - - /// - /// Gets or sets the ending file offset of the region. - /// - public int EndOffset { get; set; } - - /// - /// Gets the starting IScriptPosition in the script. - /// (Currently unimplemented.) - /// - IScriptPosition IScriptExtent.StartScriptPosition => throw new NotImplementedException(); - - /// - /// Gets the ending IScriptPosition in the script. - /// (Currently unimplemented.) - /// - IScriptPosition IScriptExtent.EndScriptPosition => throw new NotImplementedException(); - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ScriptRegion class from an - /// instance of an IScriptExtent implementation. - /// - /// - /// The IScriptExtent to copy into the ScriptRegion. - /// - /// - /// A new ScriptRegion instance with the same details as the IScriptExtent. - /// - public static ScriptRegion Create(IScriptExtent scriptExtent) - { - // IScriptExtent throws an ArgumentOutOfRange exception if Text is null - string scriptExtentText; - try - { - scriptExtentText = scriptExtent.Text; - } - catch (ArgumentOutOfRangeException) - { - scriptExtentText = string.Empty; - } - - return new ScriptRegion - { - File = scriptExtent.File, - Text = scriptExtentText, - StartLineNumber = scriptExtent.StartLineNumber, - StartColumnNumber = scriptExtent.StartColumnNumber, - StartOffset = scriptExtent.StartOffset, - EndLineNumber = scriptExtent.EndLineNumber, - EndColumnNumber = scriptExtent.EndColumnNumber, - EndOffset = scriptExtent.EndOffset - }; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs deleted file mode 100644 index 104e58446..000000000 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ /dev/null @@ -1,696 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using System.Security; -using System.Text; -using System.Runtime.InteropServices; -using Microsoft.Extensions.FileSystemGlobbing; -using Microsoft.Extensions.FileSystemGlobbing.Abstractions; - -namespace Microsoft.PowerShell.EditorServices -{ - /// - /// Manages a "workspace" of script files that are open for a particular - /// editing session. Also helps to navigate references between ScriptFiles. - /// - public class Workspace - { - #region Private Fields - - // List of all file extensions considered PowerShell files in the .Net Core Framework. - private static readonly string[] s_psFileExtensionsCoreFramework = - { - ".ps1", - ".psm1", - ".psd1" - }; - - // .Net Core doesn't appear to use the same three letter pattern matching rule although the docs - // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1'. - // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_ - private static readonly string[] s_psFileExtensionsFullFramework = - { - ".ps1", - ".psm1", - ".psd1", - ".ps1xml" - }; - - // An array of globs which includes everything. - private static readonly string[] s_psIncludeAllGlob = new [] - { - "**/*" - }; - - private ILogger logger; - private Version powerShellVersion; - private Dictionary workspaceFiles = new Dictionary(); - - #endregion - - #region Properties - - /// - /// Gets or sets the root path of the workspace. - /// - public string WorkspacePath { get; set; } - - /// - /// Gets or sets the default list of file globs to exclude during workspace searches. - /// - public List ExcludeFilesGlob { get; set; } - - /// - /// Gets or sets whether the workspace should follow symlinks in search operations. - /// - public bool FollowSymlinks { get; set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the Workspace class. - /// - /// The version of PowerShell for which scripts will be parsed. - /// An ILogger implementation used for writing log messages. - public Workspace(Version powerShellVersion, ILogger logger) - { - this.powerShellVersion = powerShellVersion; - this.logger = logger; - this.ExcludeFilesGlob = new List(); - this.FollowSymlinks = true; - } - - #endregion - - #region Public Methods - - /// - /// Creates a new ScriptFile instance which is identified by the given file - /// path and initially contains the given buffer contents. - /// - /// The file path for which a buffer will be retrieved. - /// The initial buffer contents if there is not an existing ScriptFile for this path. - /// A ScriptFile instance for the specified path. - public ScriptFile CreateScriptFileFromFileBuffer(string filePath, string initialBuffer) - { - Validate.IsNotNullOrEmptyString("filePath", filePath); - - // Resolve the full file path - string resolvedFilePath = this.ResolveFilePath(filePath); - string keyName = resolvedFilePath.ToLower(); - - ScriptFile scriptFile = - new ScriptFile( - resolvedFilePath, - filePath, - initialBuffer, - this.powerShellVersion); - - this.workspaceFiles[keyName] = scriptFile; - - this.logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); - - return scriptFile; - } - - /// - /// Gets an open file in the workspace. If the file isn't open but exists on the filesystem, load and return it. - /// IMPORTANT: Not all documents have a backing file e.g. untitled: scheme documents. Consider using - /// instead. - /// - /// The file path at which the script resides. - /// - /// is not found. - /// - /// - /// contains a null or empty string. - /// - public ScriptFile GetFile(string filePath) - { - Validate.IsNotNullOrEmptyString("filePath", filePath); - - // Resolve the full file path - string resolvedFilePath = this.ResolveFilePath(filePath); - string keyName = resolvedFilePath.ToLower(); - - // Make sure the file isn't already loaded into the workspace - ScriptFile scriptFile = null; - if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) - { - // This method allows FileNotFoundException to bubble up - // if the file isn't found. - using (FileStream fileStream = new FileStream(resolvedFilePath, FileMode.Open, FileAccess.Read)) - using (StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8)) - { - scriptFile = - new ScriptFile( - resolvedFilePath, - filePath, - streamReader, - this.powerShellVersion); - - this.workspaceFiles.Add(keyName, scriptFile); - } - - this.logger.Write(LogLevel.Verbose, "Opened file on disk: " + resolvedFilePath); - } - - return scriptFile; - } - - /// - /// Tries to get an open file in the workspace. Returns true if it succeeds, false otherwise. - /// - /// The file path at which the script resides. - /// The out parameter that will contain the ScriptFile object. - public bool TryGetFile(string filePath, out ScriptFile scriptFile) - { - var fileUri = new Uri(filePath); - - switch (fileUri.Scheme) - { - // List supported schemes here - case "file": - case "untitled": - break; - - default: - scriptFile = null; - return false; - } - - try - { - scriptFile = GetFile(filePath); - return true; - } - catch (Exception e) when ( - e is NotSupportedException || - e is FileNotFoundException || - e is DirectoryNotFoundException || - e is PathTooLongException || - e is IOException || - e is SecurityException || - e is UnauthorizedAccessException) - { - this.logger.WriteHandledException($"Failed to get file for {nameof(filePath)}: '{filePath}'", e); - scriptFile = null; - return false; - } - } - - /// - /// Gets a new ScriptFile instance which is identified by the given file path. - /// - /// The file path for which a buffer will be retrieved. - /// A ScriptFile instance if there is a buffer for the path, null otherwise. - public ScriptFile GetFileBuffer(string filePath) - { - return this.GetFileBuffer(filePath, null); - } - - /// - /// Gets a new ScriptFile instance which is identified by the given file - /// path and initially contains the given buffer contents. - /// - /// The file path for which a buffer will be retrieved. - /// The initial buffer contents if there is not an existing ScriptFile for this path. - /// A ScriptFile instance for the specified path. - public ScriptFile GetFileBuffer(string filePath, string initialBuffer) - { - Validate.IsNotNullOrEmptyString("filePath", filePath); - - // Resolve the full file path - string resolvedFilePath = this.ResolveFilePath(filePath); - string keyName = resolvedFilePath.ToLower(); - - // Make sure the file isn't already loaded into the workspace - ScriptFile scriptFile = null; - if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile) && initialBuffer != null) - { - scriptFile = - new ScriptFile( - resolvedFilePath, - filePath, - initialBuffer, - this.powerShellVersion); - - this.workspaceFiles.Add(keyName, scriptFile); - - this.logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); - } - - return scriptFile; - } - - /// - /// Gets an array of all opened ScriptFiles in the workspace. - /// - /// An array of all opened ScriptFiles in the workspace. - public ScriptFile[] GetOpenedFiles() - { - return workspaceFiles.Values.ToArray(); - } - - /// - /// Closes a currently open script file with the given file path. - /// - /// The file path at which the script resides. - public void CloseFile(ScriptFile scriptFile) - { - Validate.IsNotNull("scriptFile", scriptFile); - - this.workspaceFiles.Remove(scriptFile.Id); - } - - /// - /// Gets all file references by recursively searching - /// through referenced files in a scriptfile - /// - /// Contains the details and contents of an open script file - /// A scriptfile array where the first file - /// in the array is the "root file" of the search - public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) - { - Dictionary referencedScriptFiles = new Dictionary(); - List expandedReferences = new List(); - - // add original file so it's not searched for, then find all file references - referencedScriptFiles.Add(scriptFile.Id, scriptFile); - RecursivelyFindReferences(scriptFile, referencedScriptFiles); - - // remove original file from referened file and add it as the first element of the - // expanded referenced list to maintain order so the original file is always first in the list - referencedScriptFiles.Remove(scriptFile.Id); - expandedReferences.Add(scriptFile); - - if (referencedScriptFiles.Count > 0) - { - expandedReferences.AddRange(referencedScriptFiles.Values); - } - - return expandedReferences.ToArray(); - } - - /// - /// Gets the workspace-relative path of the given file path. - /// - /// The original full file path. - /// A relative file path - public string GetRelativePath(string filePath) - { - string resolvedPath = filePath; - - if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(this.WorkspacePath)) - { - Uri workspaceUri = new Uri(this.WorkspacePath); - Uri fileUri = new Uri(filePath); - - resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString(); - - // Convert the directory separators if necessary - if (System.IO.Path.DirectorySeparatorChar == '\\') - { - resolvedPath = resolvedPath.Replace('/', '\\'); - } - } - - return resolvedPath; - } - - /// - /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner, using default values. - /// - /// An enumerator over the PowerShell files found in the workspace. - public IEnumerable EnumeratePSFiles() - { - return EnumeratePSFiles( - ExcludeFilesGlob.ToArray(), - s_psIncludeAllGlob, - maxDepth: 64, - ignoreReparsePoints: !FollowSymlinks - ); - } - - /// - /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner. - /// - /// An enumerator over the PowerShell files found in the workspace. - public IEnumerable EnumeratePSFiles( - string[] excludeGlobs, - string[] includeGlobs, - int maxDepth, - bool ignoreReparsePoints - ) - { - if (WorkspacePath == null || !Directory.Exists(WorkspacePath)) - { - yield break; - } - - var matcher = new Microsoft.Extensions.FileSystemGlobbing.Matcher(); - foreach (string pattern in includeGlobs) { matcher.AddInclude(pattern); } - foreach (string pattern in excludeGlobs) { matcher.AddExclude(pattern); } - - var fsFactory = new WorkspaceFileSystemWrapperFactory( - WorkspacePath, - maxDepth, - Utils.IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework, - ignoreReparsePoints, - logger - ); - var fileMatchResult = matcher.Execute(fsFactory.RootDirectory); - foreach (FilePatternMatch item in fileMatchResult.Files) - { - yield return Path.Combine(WorkspacePath, item.Path); - } - } - - #endregion - - #region Private Methods - /// - /// Recusrively searches through referencedFiles in scriptFiles - /// and builds a Dictonary of the file references - /// - /// Details an contents of "root" script file - /// A Dictionary of referenced script files - private void RecursivelyFindReferences( - ScriptFile scriptFile, - Dictionary referencedScriptFiles) - { - // Get the base path of the current script for use in resolving relative paths - string baseFilePath = - GetBaseFilePath( - scriptFile.FilePath); - - foreach (string referencedFileName in scriptFile.ReferencedFiles) - { - string resolvedScriptPath = - this.ResolveRelativeScriptPath( - baseFilePath, - referencedFileName); - - // If there was an error resolving the string, skip this reference - if (resolvedScriptPath == null) - { - continue; - } - - this.logger.Write( - LogLevel.Verbose, - string.Format( - "Resolved relative path '{0}' to '{1}'", - referencedFileName, - resolvedScriptPath)); - - // Get the referenced file if it's not already in referencedScriptFiles - if (this.TryGetFile(resolvedScriptPath, out ScriptFile referencedFile)) - { - // Normalize the resolved script path and add it to the - // referenced files list if it isn't there already - resolvedScriptPath = resolvedScriptPath.ToLower(); - if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) - { - referencedScriptFiles.Add(resolvedScriptPath, referencedFile); - RecursivelyFindReferences(referencedFile, referencedScriptFiles); - } - } - } - } - - internal string ResolveFilePath(string filePath) - { - if (!IsPathInMemory(filePath)) - { - if (filePath.StartsWith(@"file://")) - { - filePath = Workspace.UnescapeDriveColon(filePath); - // Client sent the path in URI format, extract the local path - filePath = new Uri(filePath).LocalPath; - } - - // Clients could specify paths with escaped space, [ and ] characters which .NET APIs - // will not handle. These paths will get appropriately escaped just before being passed - // into the PowerShell engine. - filePath = PowerShellContext.UnescapeWildcardEscapedPath(filePath); - - // Get the absolute file path - filePath = Path.GetFullPath(filePath); - } - - this.logger.Write(LogLevel.Verbose, "Resolved path: " + filePath); - - return filePath; - } - - internal static bool IsPathInMemory(string filePath) - { - bool isInMemory = false; - - // In cases where a "virtual" file is displayed in the editor, - // we need to treat the file differently than one that exists - // on disk. A virtual file could be something like a diff - // view of the current file or an untitled file. - try - { - // File system absoulute paths will have a URI scheme of file:. - // Other schemes like "untitled:" and "gitlens-git:" will return false for IsFile. - var uri = new Uri(filePath); - isInMemory = !uri.IsFile; - } - catch (UriFormatException) - { - // Relative file paths cause a UriFormatException. - // In this case, fallback to using Path.GetFullPath(). - try - { - Path.GetFullPath(filePath); - } - catch (Exception ex) when (ex is ArgumentException || ex is NotSupportedException) - { - isInMemory = true; - } - catch (PathTooLongException) - { - // If we ever get here, it should be an actual file so, not in memory - } - } - - return isInMemory; - } - - private string GetBaseFilePath(string filePath) - { - if (IsPathInMemory(filePath)) - { - // If the file is in memory, use the workspace path - return this.WorkspacePath; - } - - if (!Path.IsPathRooted(filePath)) - { - // TODO: Assert instead? - throw new InvalidOperationException( - string.Format( - "Must provide a full path for originalScriptPath: {0}", - filePath)); - } - - // Get the directory of the file path - return Path.GetDirectoryName(filePath); - } - - internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath) - { - string combinedPath = null; - Exception resolveException = null; - - try - { - // If the path is already absolute there's no need to resolve it relatively - // to the baseFilePath. - if (Path.IsPathRooted(relativePath)) - { - return relativePath; - } - - // Get the directory of the original script file, combine it - // with the given path and then resolve the absolute file path. - combinedPath = - Path.GetFullPath( - Path.Combine( - baseFilePath, - relativePath)); - } - catch (NotSupportedException e) - { - // Occurs if the path is incorrectly formatted for any reason. One - // instance where this occurred is when a user had curly double-quote - // characters in their source instead of normal double-quotes. - resolveException = e; - } - catch (ArgumentException e) - { - // Occurs if the path contains invalid characters, specifically those - // listed in System.IO.Path.InvalidPathChars. - resolveException = e; - } - - if (resolveException != null) - { - this.logger.Write( - LogLevel.Error, - $"Could not resolve relative script path\r\n" + - $" baseFilePath = {baseFilePath}\r\n " + - $" relativePath = {relativePath}\r\n\r\n" + - $"{resolveException.ToString()}"); - } - - return combinedPath; - } - - /// - /// Takes a file-scheme URI with an escaped colon after the drive letter and unescapes only the colon. - /// VSCode sends escaped colons after drive letters, but System.Uri expects unescaped. - /// - /// The fully-escaped file-scheme URI string. - /// A file-scheme URI string with the drive colon unescaped. - private static string UnescapeDriveColon(string fileUri) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return fileUri; - } - - // Check here that we have something like "file:///C%3A/" as a prefix (caller must check the file:// part) - if (!(fileUri[7] == '/' && - char.IsLetter(fileUri[8]) && - fileUri[9] == '%' && - fileUri[10] == '3' && - fileUri[11] == 'A' && - fileUri[12] == '/')) - { - return fileUri; - } - - var sb = new StringBuilder(fileUri.Length - 2); // We lost "%3A" and gained ":", so length - 2 - sb.Append("file:///"); - sb.Append(fileUri[8]); // The drive letter - sb.Append(':'); - sb.Append(fileUri.Substring(12)); // The rest of the URI after the colon - - return sb.ToString(); - } - - /// - /// Converts a file system path into a DocumentUri required by Language Server Protocol. - /// - /// - /// When sending a document path to a LSP client, the path must be provided as a - /// DocumentUri in order to features like the Problems window or peek definition - /// to be able to open the specified file. - /// - /// - /// A file system path. Note: if the path is already a DocumentUri, it will be returned unmodified. - /// - /// The file system path encoded as a DocumentUri. - public static string ConvertPathToDocumentUri(string path) - { - const string fileUriPrefix = "file:"; - const string untitledUriPrefix = "untitled:"; - - // If path is already in document uri form, there is nothing to convert. - if (path.StartsWith(untitledUriPrefix, StringComparison.Ordinal) || - path.StartsWith(fileUriPrefix, StringComparison.Ordinal)) - { - return path; - } - - string escapedPath = Uri.EscapeDataString(path); - - // Max capacity of the StringBuilder will be the current escapedPath length - // plus extra chars for file:///. - var docUriStrBld = new StringBuilder(escapedPath.Length + fileUriPrefix.Length + 3); - docUriStrBld.Append(fileUriPrefix).Append("//"); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // VSCode file URIs on Windows need the drive letter to be lowercase. Search the - // original path for colon since a char search (no string culture involved) is - // faster than a string search. If found, then lowercase the associated drive letter. - if (path.Contains(':')) - { - // A valid, drive-letter based path converted to URI form needs to be prefixed - // with a / to indicate the path is an absolute path. - docUriStrBld.Append("/"); - int prefixLen = docUriStrBld.Length; - - docUriStrBld.Append(escapedPath); - - // Uri.EscapeDataString goes a bit far, encoding \ chars. Also, VSCode wants / instead of \. - docUriStrBld.Replace("%5C", "/"); - - // Find the first colon after the "file:///" prefix, skipping the first char after - // the prefix since a Windows path cannot start with a colon. End the check at - // less than docUriStrBld.Length - 2 since we need to look-ahead two characters. - for (int i = prefixLen + 1; i < docUriStrBld.Length - 2; i++) - { - if ((docUriStrBld[i] == '%') && (docUriStrBld[i + 1] == '3') && (docUriStrBld[i + 2] == 'A')) - { - int driveLetterIndex = i - 1; - char driveLetter = char.ToLowerInvariant(docUriStrBld[driveLetterIndex]); - docUriStrBld.Replace(docUriStrBld[driveLetterIndex], driveLetter, driveLetterIndex, 1); - break; - } - } - } - else - { - // This is a Windows path without a drive specifier, must be either a relative or UNC path. - int prefixLen = docUriStrBld.Length; - - docUriStrBld.Append(escapedPath); - - // Uri.EscapeDataString goes a bit far, encoding \ chars. Also, VSCode wants / instead of \. - docUriStrBld.Replace("%5C", "/"); - - // The proper URI form for a UNC path is file://server/share. In the case of a UNC - // path, remove the path's leading // because the file:// prefix already provides it. - if ((docUriStrBld.Length > prefixLen + 1) && - (docUriStrBld[prefixLen] == '/') && - (docUriStrBld[prefixLen + 1] == '/')) - { - docUriStrBld.Remove(prefixLen, 2); - } - } - } - else - { - // On non-Windows systems, append the escapedPath and undo the over-aggressive - // escaping of / done by Uri.EscapeDataString. - docUriStrBld.Append(escapedPath).Replace("%2F", "/"); - } - - if (!Utils.IsNetCore) - { - // ' is not encoded by Uri.EscapeDataString in Windows PowerShell 5.x. - // This is apparently a difference between .NET Framework and .NET Core. - docUriStrBld.Replace("'", "%27"); - } - - return docUriStrBld.ToString(); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Workspace/WorkspaceFileSystemWrapper.cs b/src/PowerShellEditorServices/Workspace/WorkspaceFileSystemWrapper.cs deleted file mode 100644 index d90d0b4ec..000000000 --- a/src/PowerShellEditorServices/Workspace/WorkspaceFileSystemWrapper.cs +++ /dev/null @@ -1,381 +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 Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Security; -using Microsoft.Extensions.FileSystemGlobbing.Abstractions; - -namespace Microsoft.PowerShell.EditorServices -{ - - /// - /// A FileSystem wrapper class which only returns files and directories that the consumer is interested in, - /// with a maximum recursion depth and silently ignores most file system errors. Typically this is used by the - /// Microsoft.Extensions.FileSystemGlobbing library. - /// - public class WorkspaceFileSystemWrapperFactory - { - private readonly DirectoryInfoBase _rootDirectory; - private readonly string[] _allowedExtensions; - private readonly bool _ignoreReparsePoints; - - /// - /// Gets the maximum depth of the directories that will be searched - /// - internal int MaxRecursionDepth { get; } - - /// - /// Gets the logging facility - /// - internal ILogger Logger { get; } - - /// - /// Gets the directory where the factory is rooted. Only files and directories at this level, or deeper, will be visible - /// by the wrapper - /// - public DirectoryInfoBase RootDirectory - { - get { return _rootDirectory; } - } - - /// - /// Creates a new FileWrapper Factory - /// - /// The path to the root directory for the factory. - /// The maximum directory depth. - /// An array of file extensions that will be visible from the factory. For example [".ps1", ".psm1"] - /// Whether objects which are Reparse Points should be ignored. https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-points - /// An ILogger implementation used for writing log messages. - public WorkspaceFileSystemWrapperFactory(String rootPath, int recursionDepthLimit, string[] allowedExtensions, bool ignoreReparsePoints, ILogger logger) - { - MaxRecursionDepth = recursionDepthLimit; - _rootDirectory = new WorkspaceFileSystemDirectoryWrapper(this, new DirectoryInfo(rootPath), 0); - _allowedExtensions = allowedExtensions; - _ignoreReparsePoints = ignoreReparsePoints; - Logger = logger; - } - - /// - /// Creates a wrapped object from . - /// - internal DirectoryInfoBase CreateDirectoryInfoWrapper(DirectoryInfo dirInfo, int depth) => - new WorkspaceFileSystemDirectoryWrapper(this, dirInfo, depth >= 0 ? depth : 0); - - /// - /// Creates a wrapped object from . - /// - internal FileInfoBase CreateFileInfoWrapper(FileInfo fileInfo, int depth) => - new WorkspaceFileSystemFileInfoWrapper(this, fileInfo, depth >= 0 ? depth : 0); - - /// - /// Enumerates all objects in the specified directory and ignores most errors - /// - internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo dirInfo) - { - // Find the subdirectories - string[] subDirs; - try - { - subDirs = Directory.GetDirectories(dirInfo.FullName, "*", SearchOption.TopDirectoryOnly); - } - catch (DirectoryNotFoundException e) - { - Logger.WriteHandledException( - $"Could not enumerate directories in the path '{dirInfo.FullName}' due to it being an invalid path", - e); - - yield break; - } - catch (PathTooLongException e) - { - Logger.WriteHandledException( - $"Could not enumerate directories in the path '{dirInfo.FullName}' due to the path being too long", - e); - - yield break; - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - Logger.WriteHandledException( - $"Could not enumerate directories in the path '{dirInfo.FullName}' due to the path not being accessible", - e); - - yield break; - } - catch (Exception e) - { - Logger.WriteHandledException( - $"Could not enumerate directories in the path '{dirInfo.FullName}' due to an exception", - e); - - yield break; - } - foreach (string dirPath in subDirs) - { - var subDirInfo = new DirectoryInfo(dirPath); - if (_ignoreReparsePoints && (subDirInfo.Attributes & FileAttributes.ReparsePoint) != 0) { continue; } - yield return subDirInfo; - } - - // Find the files - string[] filePaths; - try - { - filePaths = Directory.GetFiles(dirInfo.FullName, "*", SearchOption.TopDirectoryOnly); - } - catch (DirectoryNotFoundException e) - { - Logger.WriteHandledException( - $"Could not enumerate files in the path '{dirInfo.FullName}' due to it being an invalid path", - e); - - yield break; - } - catch (PathTooLongException e) - { - Logger.WriteHandledException( - $"Could not enumerate files in the path '{dirInfo.FullName}' due to the path being too long", - e); - - yield break; - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - Logger.WriteHandledException( - $"Could not enumerate files in the path '{dirInfo.FullName}' due to the path not being accessible", - e); - - yield break; - } - catch (Exception e) - { - Logger.WriteHandledException( - $"Could not enumerate files in the path '{dirInfo.FullName}' due to an exception", - e); - - yield break; - } - foreach (string filePath in filePaths) - { - var fileInfo = new FileInfo(filePath); - if (_allowedExtensions == null || _allowedExtensions.Length == 0) { yield return fileInfo; continue; } - if (_ignoreReparsePoints && (fileInfo.Attributes & FileAttributes.ReparsePoint) != 0) { continue; } - foreach (string extension in _allowedExtensions) - { - if (fileInfo.Extension == extension) { yield return fileInfo; break; } - } - } - } - } - - /// - /// Wraps an instance of and provides implementation of - /// . - /// Based on https://github.com/aspnet/Extensions/blob/c087cadf1dfdbd2b8785ef764e5ef58a1a7e5ed0/src/FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.cs - /// - public class WorkspaceFileSystemDirectoryWrapper : DirectoryInfoBase - { - private readonly DirectoryInfo _concreteDirectoryInfo; - private readonly bool _isParentPath; - private readonly WorkspaceFileSystemWrapperFactory _fsWrapperFactory; - private readonly int _depth; - - /// - /// Initializes an instance of . - /// - public WorkspaceFileSystemDirectoryWrapper(WorkspaceFileSystemWrapperFactory factory, DirectoryInfo directoryInfo, int depth) - { - _concreteDirectoryInfo = directoryInfo; - _isParentPath = (depth == 0); - _fsWrapperFactory = factory; - _depth = depth; - } - - /// - public override IEnumerable EnumerateFileSystemInfos() - { - if (!_concreteDirectoryInfo.Exists || _depth >= _fsWrapperFactory.MaxRecursionDepth) { yield break; } - foreach (FileSystemInfo fileSystemInfo in _fsWrapperFactory.SafeEnumerateFileSystemInfos(_concreteDirectoryInfo)) - { - switch (fileSystemInfo) - { - case DirectoryInfo dirInfo: - yield return _fsWrapperFactory.CreateDirectoryInfoWrapper(dirInfo, _depth + 1); - break; - case FileInfo fileInfo: - yield return _fsWrapperFactory.CreateFileInfoWrapper(fileInfo, _depth); - break; - default: - // We should NEVER get here, but if we do just continue on - break; - } - } - } - - /// - /// Returns an instance of that represents a subdirectory. - /// - /// - /// If equals '..', this returns the parent directory. - /// - /// The directory name. - /// The directory - public override DirectoryInfoBase GetDirectory(string name) - { - bool isParentPath = string.Equals(name, "..", StringComparison.Ordinal); - - if (isParentPath) { return ParentDirectory; } - - var dirs = _concreteDirectoryInfo.GetDirectories(name); - - if (dirs.Length == 1) { return _fsWrapperFactory.CreateDirectoryInfoWrapper(dirs[0], _depth + 1); } - if (dirs.Length == 0) { return null; } - // This shouldn't happen. The parameter name isn't supposed to contain wild card. - throw new InvalidOperationException( - string.Format( - System.Globalization.CultureInfo.CurrentCulture, - "More than one sub directories are found under {0} with name {1}.", - _concreteDirectoryInfo.FullName, name)); - } - - /// - public override FileInfoBase GetFile(string name) => _fsWrapperFactory.CreateFileInfoWrapper(new FileInfo(Path.Combine(_concreteDirectoryInfo.FullName, name)), _depth); - - /// - public override string Name => _isParentPath ? ".." : _concreteDirectoryInfo.Name; - - /// - /// Returns the full path to the directory. - /// - public override string FullName => _concreteDirectoryInfo.FullName; - - /// - /// Safely calculates the parent of this directory, swallowing most errors. - /// - private DirectoryInfoBase SafeParentDirectory() - { - try - { - return _fsWrapperFactory.CreateDirectoryInfoWrapper(_concreteDirectoryInfo.Parent, _depth - 1); - } - catch (DirectoryNotFoundException e) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to it being an invalid path", - e); - } - catch (PathTooLongException e) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to the path being too long", - e); - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to the path not being accessible", - e); - } - catch (Exception e) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to an exception", - e); - } - return null; - } - - /// - /// Returns the parent directory. (Overrides ). - /// - public override DirectoryInfoBase ParentDirectory - { - get - { - return SafeParentDirectory(); - } - } - } - - /// - /// Wraps an instance of to provide implementation of . - /// - public class WorkspaceFileSystemFileInfoWrapper : FileInfoBase - { - private readonly FileInfo _concreteFileInfo; - private readonly WorkspaceFileSystemWrapperFactory _fsWrapperFactory; - private readonly int _depth; - - /// - /// Initializes instance of to wrap the specified object . - /// - public WorkspaceFileSystemFileInfoWrapper(WorkspaceFileSystemWrapperFactory factory, FileInfo fileInfo, int depth) - { - _fsWrapperFactory = factory; - _concreteFileInfo = fileInfo; - _depth = depth; - } - - /// - /// The file name. (Overrides ). - /// - public override string Name => _concreteFileInfo.Name; - - /// - /// The full path of the file. (Overrides ). - /// - public override string FullName => _concreteFileInfo.FullName; - - /// - /// Safely calculates the parent of this file, swallowing most errors. - /// - private DirectoryInfoBase SafeParentDirectory() - { - try - { - return _fsWrapperFactory.CreateDirectoryInfoWrapper(_concreteFileInfo.Directory, _depth); - } - catch (DirectoryNotFoundException e) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteFileInfo.FullName}' due to it being an invalid path", - e); - } - catch (PathTooLongException e) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteFileInfo.FullName}' due to the path being too long", - e); - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteFileInfo.FullName}' due to the path not being accessible", - e); - } - catch (Exception e) - { - _fsWrapperFactory.Logger.WriteHandledException( - $"Could not get parent of '{_concreteFileInfo.FullName}' due to an exception", - e); - } - return null; - } - - /// - /// The directory containing the file. (Overrides ). - /// - public override DirectoryInfoBase ParentDirectory - { - get - { - return SafeParentDirectory(); - } - } - } -}