diff --git a/.gitignore b/.gitignore index 3567788f8..8d18bc259 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ docs/metadata/ *.zip # Generated build info file -src/PowerShellEditorServices.Host/BuildInfo/BuildInfo.cs +src/PowerShellEditorServices/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/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index 905802378..0ccaff133 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -23,3 +23,4 @@ steps: inputs: ArtifactName: PowerShellEditorServices-CI PathtoPublish: '$(Build.ArtifactStagingDirectory)' + condition: succeededOrFailed() diff --git a/NuGet.Config b/NuGet.Config index 6efc7f7b9..2f64451ed 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,4 +3,8 @@ + + + + diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 91046afb7..aa32c403e 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -22,13 +22,12 @@ $script:IsUnix = $PSVersionTable.PSEdition -and $PSVersionTable.PSEdition -eq "C $script:TargetPlatform = "netstandard2.0" $script:TargetFrameworksParam = "/p:TargetFrameworks=`"$script:TargetPlatform`"" $script:RequiredSdkVersion = (Get-Content (Join-Path $PSScriptRoot 'global.json') | ConvertFrom-Json).sdk.version -$script:MinimumPesterVersion = '4.7' $script:NugetApiUriBase = 'https://www.nuget.org/api/v2/package' $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", "Hosting", "BuildInfo.cs") $script:PSCoreModulePath = $null @@ -52,27 +51,28 @@ Schema is: $script:RequiredBuildAssets = @{ $script:ModuleBinPath = @{ 'PowerShellEditorServices' = @( - 'publish/Serilog.dll', - 'publish/Serilog.Sinks.Async.dll', - 'publish/Serilog.Sinks.Console.dll', - 'publish/Serilog.Sinks.File.dll', + 'publish/Microsoft.Extensions.DependencyInjection.Abstractions.dll', + 'publish/Microsoft.Extensions.DependencyInjection.dll', 'publish/Microsoft.Extensions.FileSystemGlobbing.dll', - 'Microsoft.PowerShell.EditorServices.dll', - 'Microsoft.PowerShell.EditorServices.pdb' - ) - - 'PowerShellEditorServices.Host' = @( - 'publish/UnixConsoleEcho.dll', - 'publish/runtimes/osx-64/native/libdisablekeyecho.dylib', - 'publish/runtimes/linux-64/native/libdisablekeyecho.so', + 'publish/Microsoft.Extensions.Logging.Abstractions.dll', + 'publish/Microsoft.Extensions.Logging.dll', + 'publish/Microsoft.Extensions.Options.dll', + 'publish/Microsoft.Extensions.Primitives.dll', + 'publish/Microsoft.PowerShell.EditorServices.dll', + 'publish/Microsoft.PowerShell.EditorServices.pdb', 'publish/Newtonsoft.Json.dll', - 'Microsoft.PowerShell.EditorServices.Host.dll', - 'Microsoft.PowerShell.EditorServices.Host.pdb' - ) - - 'PowerShellEditorServices.Protocol' = @( - 'Microsoft.PowerShell.EditorServices.Protocol.dll', - 'Microsoft.PowerShell.EditorServices.Protocol.pdb' + 'publish/OmniSharp.Extensions.JsonRpc.dll', + 'publish/OmniSharp.Extensions.LanguageProtocol.dll', + 'publish/OmniSharp.Extensions.LanguageServer.dll', + 'publish/OmniSharp.Extensions.DebugAdapter.dll', + 'publish/OmniSharp.Extensions.DebugAdapter.Server.dll', + 'publish/runtimes/linux-64/native/libdisablekeyecho.so', + 'publish/runtimes/osx-64/native/libdisablekeyecho.dylib', + 'publish/Serilog.dll', + 'publish/Serilog.Extensions.Logging.dll', + 'publish/Serilog.Sinks.File.dll', + 'publish/System.Reactive.dll', + 'publish/UnixConsoleEcho.dll' ) } @@ -102,12 +102,6 @@ $script:RequiredNugetBinaries = @{ @{ PackageName = 'System.Security.AccessControl'; PackageVersion = '4.5.0'; TargetRuntime = 'net461' }, @{ PackageName = 'System.IO.Pipes.AccessControl'; PackageVersion = '4.5.1'; TargetRuntime = 'net461' } ) - - '6.0' = @( - @{ PackageName = 'System.Security.Principal.Windows'; PackageVersion = '4.5.0'; TargetRuntime = 'netcoreapp2.0' }, - @{ PackageName = 'System.Security.AccessControl'; PackageVersion = '4.5.0'; TargetRuntime = 'netcoreapp2.0' }, - @{ PackageName = 'System.IO.Pipes.AccessControl'; PackageVersion = '4.5.1'; TargetRuntime = 'netstandard2.0' } - ) } if (Get-Command git -ErrorAction SilentlyContinue) { @@ -180,7 +174,7 @@ function Invoke-WithCreateDefaultHook { } } -task SetupDotNet -Before Clean, Build, TestHost, TestServer, TestProtocol, PackageNuGet { +task SetupDotNet -Before Clean, Build, TestHost, TestServer, TestProtocol, TestE2E { $dotnetPath = "$PSScriptRoot/.dotnet" $dotnetExePath = if ($script:IsUnix) { "$dotnetPath/dotnet" } else { "$dotnetPath/dotnet.exe" } @@ -259,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 @@ -310,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.Hosting { public static class BuildInfo { @@ -326,21 +320,16 @@ 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.Host\PowerShellEditorServices.Host.csproj -f $script:TargetPlatform } exec { & $script:dotnetExe build -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj $script:TargetFrameworksParam } } -task BuildPsesClientModule SetupDotNet,{ - Write-Verbose 'Building PsesPsClient testing module' - & $PSScriptRoot/tools/PsesPsClient/build.ps1 -DotnetExe $script:dotnetExe -} - function DotNetTestFilter { # Reference https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests if ($TestFilter) { @("--filter",$TestFilter) } else { "" } } -task Test TestServer,TestProtocol,TestPester +# task Test TestServer,TestProtocol,TestE2E +task Test TestE2E task TestServer { Set-Location .\test\PowerShellEditorServices.Test\ @@ -378,26 +367,11 @@ task TestHost { exec { & $script:dotnetExe test -f $script:TestRuntime.Core (DotNetTestFilter) } } -task TestPester Build,BuildPsesClientModule,EnsurePesterInstalled,{ - $testParams = @{} - if ($env:TF_BUILD) - { - $testParams += @{ - OutputFormat = 'NUnitXml' - OutputFile = 'TestResults.xml' - } - } - $result = Invoke-Pester "$PSScriptRoot/test/Pester/" @testParams -PassThru +task TestE2E { + Set-Location .\test\PowerShellEditorServices.Test.E2E\ - if ($result.FailedCount -gt 0) - { - throw "$($result.FailedCount) tests failed." - } -} - -task EnsurePesterInstalled -If (-not (Get-Module Pester -ListAvailable | Where-Object Version -ge $script:MinimumPesterVersion)) { - Write-Warning "Required Pester version not found, installing Pester to current user scope" - Install-Module -Scope CurrentUser Pester -Force -SkipPublisherCheck + $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } + exec { & $script:dotnetExe test --logger trx -f $script:TestRuntime.Core (DotNetTestFilter) } } task LayoutModule -After Build { @@ -502,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 { @@ -523,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 6c3671bb0..30c954422 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,12 +18,14 @@ 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}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices", "src\PowerShellEditorServices\PowerShellEditorServices.csproj", "{29EEDF03-0990-45F4-846E-2616970D1FA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Test.E2E", "test\PowerShellEditorServices.Test.E2E\PowerShellEditorServices.Test.E2E.csproj", "{2561F253-8F72-436A-BCC3-AA63AB82EDC0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,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 @@ -98,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 @@ -134,18 +96,41 @@ Global {3B38E8DA-8BFF-4264-AF16-47929E6398A3}.Release|x64.Build.0 = Release|Any CPU {3B38E8DA-8BFF-4264-AF16-47929E6398A3}.Release|x86.ActiveCfg = Release|Any CPU {3B38E8DA-8BFF-4264-AF16-47929E6398A3}.Release|x86.Build.0 = Release|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Debug|x64.ActiveCfg = Debug|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Debug|x64.Build.0 = Debug|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Debug|x86.ActiveCfg = Debug|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Debug|x86.Build.0 = Debug|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Release|Any CPU.Build.0 = Release|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Release|x64.ActiveCfg = Release|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Release|x64.Build.0 = Release|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Release|x86.ActiveCfg = Release|Any CPU + {29EEDF03-0990-45F4-846E-2616970D1FA2}.Release|x86.Build.0 = Release|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Debug|x64.ActiveCfg = Debug|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Debug|x64.Build.0 = Debug|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Debug|x86.ActiveCfg = Debug|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Debug|x86.Build.0 = Debug|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Release|Any CPU.Build.0 = Release|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Release|x64.ActiveCfg = Release|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Release|x64.Build.0 = Release|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Release|x86.ActiveCfg = Release|Any CPU + {2561F253-8F72-436A-BCC3-AA63AB82EDC0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution 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} + {2561F253-8F72-436A-BCC3-AA63AB82EDC0} = {422E561A-8118-4BE7-A54F-9309E4F03AAE} EndGlobalSection EndGlobal diff --git a/docs/api/index.md b/docs/api/index.md index a845c1d66..c53ce056c 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -20,9 +20,9 @@ the PowerShell debugger. Use the @Microsoft.PowerShell.EditorServices.Console.ConsoleService to provide interactive console support in the user's editor. -Use the @Microsoft.PowerShell.EditorServices.Extensions.ExtensionService to allow +Use the @Microsoft.PowerShell.EditorServices.Services.ExtensionService to allow the user to extend the host editor with new capabilities using PowerShell code. The core of all the services is the @Microsoft.PowerShell.EditorServices.PowerShellContext class. This class manages a session's runspace and handles script and command -execution no matter what state the runspace is in. \ No newline at end of file +execution no matter what state the runspace is in. diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md index 6e964416c..b37c032a6 100644 --- a/docs/guide/extensions.md +++ b/docs/guide/extensions.md @@ -9,7 +9,7 @@ uses PowerShell Editor Services. ### Introducing `$psEditor` The entry point for the PowerShell Editor Services extensibility model is the `$psEditor` -object of the type @Microsoft.PowerShell.EditorServices.Extensions.EditorObject. For +object of the type @Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorObject. For those familiar with the PowerShell ISE's `$psISE` object, the `$psEditor` object is very similar. The primary difference is that this model has been generalized to work against any editor which leverages PowerShell Editor Services for its PowerShell editing experience. @@ -19,7 +19,7 @@ any editor which leverages PowerShell Editor Services for its PowerShell editing > please file an issue on our GitHub page. This object gives access to all of the high-level services in the current -editing session. For example, the @Microsoft.PowerShell.EditorServices.Extensions.EditorObject.Workspace +editing session. For example, the @Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorObject.Workspace property gives access to the editor's workspace, allowing you to create or open files in the editor. @@ -79,17 +79,17 @@ Register-EditorCommand ` -ScriptBlock { Write-Output "My command's script block was invoked!" } ``` -### The @Microsoft.PowerShell.EditorServices.Extensions.EditorContext parameter +### The @Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext parameter Your function, cmdlet, or ScriptBlock can optionally accept a single parameter -of type @Microsoft.PowerShell.EditorServices.Extensions.EditorContext which provides +of type @Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext which provides information about the state of the host editor at the time your command was invoked. With this object you can easily perform operations like manipulatin the state of the user's active editor buffer or changing the current selection. The usual convention is that a `$context` parameter is added to your editor command's function. For now it is recommended that you fully specify the -type of the @Microsoft.PowerShell.EditorServices.Extensions.EditorContext object +type of the @Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext object so that you get full IntelliSense on your context parameter. Here is an example of using the `$context` parameter: @@ -99,7 +99,7 @@ Register-EditorCommand ` -Name "MyModule.MyEditorCommandWithContext" ` -DisplayName "My command with context usage" ` -ScriptBlock { - param([Microsoft.PowerShell.EditorServices.Extensions.EditorContext]$context) + param([Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext]$context) Write-Output "The user's cursor is on line $($context.CursorPosition.Line)!" } ``` @@ -165,4 +165,4 @@ in that editor starts up. > NOTE: In the future we plan to provide an easy way for the user to opt-in > to the automatic loading of any editor command modules that they've installed > from the PowerShell Gallery. If this interests you, please let us know on -> [this GitHub issue](https://github.com/PowerShell/PowerShellEditorServices/issues/215). \ No newline at end of file +> [this GitHub issue](https://github.com/PowerShell/PowerShellEditorServices/issues/215). 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 e7b34e076..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.Extensions.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 0e9088bd3..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.Extensions.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/module/PowerShellEditorServices/Commands/Private/BuiltInCommands.ps1 b/module/PowerShellEditorServices/Commands/Private/BuiltInCommands.ps1 index 7c73e8358..e56e44af7 100644 --- a/module/PowerShellEditorServices/Commands/Private/BuiltInCommands.ps1 +++ b/module/PowerShellEditorServices/Commands/Private/BuiltInCommands.ps1 @@ -3,7 +3,7 @@ Register-EditorCommand ` -DisplayName 'Open Editor Profile' ` -SuppressOutput ` -ScriptBlock { - param([Microsoft.PowerShell.EditorServices.Extensions.EditorContext]$context) + param([Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext]$context) If (!(Test-Path -Path $Profile)) { New-Item -Path $Profile -ItemType File } $psEditor.Workspace.OpenFile($Profile) } @@ -13,18 +13,18 @@ Register-EditorCommand ` -DisplayName 'Open Profile from List (Current User)' ` -SuppressOutput ` -ScriptBlock { - param([Microsoft.PowerShell.EditorServices.Extensions.EditorContext]$context) - - $Current = Split-Path -Path $profile -Leaf + param([Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext]$context) + + $Current = Split-Path -Path $profile -Leaf $List = @($Current,'Microsoft.VSCode_profile.ps1','Microsoft.PowerShell_profile.ps1','Microsoft.PowerShellISE_profile.ps1','Profile.ps1') | Select-Object -Unique $Choices = [System.Management.Automation.Host.ChoiceDescription[]] @($List) $Selection = $host.ui.PromptForChoice('Please Select a Profile', '(Current User)', $choices,'0') $Name = $List[$Selection] - + $ProfileDir = Split-Path $Profile -Parent $ProfileName = Join-Path -Path $ProfileDir -ChildPath $Name - + If (!(Test-Path -Path $ProfileName)) { New-Item -Path $ProfileName -ItemType File } - + $psEditor.Workspace.OpenFile($ProfileName) - } \ No newline at end of file + } diff --git a/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 b/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 index 47623296a..9018680d9 100644 --- a/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 @@ -47,7 +47,7 @@ function Register-EditorCommand { $commandArgs += $Function } - $editorCommand = New-Object Microsoft.PowerShell.EditorServices.Extensions.EditorCommand -ArgumentList $commandArgs + $editorCommand = New-Object Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommand -ArgumentList $commandArgs if ($psEditor.RegisterCommand($editorCommand)) { Write-Verbose "Registered new command '$Name'" diff --git a/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 b/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 index 9ddec5021..e57823a3f 100644 --- a/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 @@ -7,7 +7,7 @@ function Import-EditorCommand { <# .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml #> - [OutputType([Microsoft.PowerShell.EditorServices.Extensions.EditorCommand])] + [OutputType([Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommand])] [CmdletBinding(DefaultParameterSetName='ByCommand')] param( [Parameter(Position=0, @@ -75,7 +75,7 @@ function Import-EditorCommand { $commands = $Command | Get-Command -ErrorAction SilentlyContinue } } - $attributeType = [Microsoft.PowerShell.EditorServices.Extensions.EditorCommandAttribute] + $attributeType = [Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommandAttribute] foreach ($aCommand in $commands) { # Get the attribute from our command to get name info. $details = $aCommand.ScriptBlock.Attributes | Where-Object TypeId -eq $attributeType @@ -99,7 +99,7 @@ function Import-EditorCommand { } # Check for a context parameter. $contextParameter = $aCommand.Parameters.Values | - Where-Object ParameterType -eq ([Microsoft.PowerShell.EditorServices.Extensions.EditorContext]) + Where-Object ParameterType -eq ([Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorContext]) # If one is found then add a named argument. Otherwise call the command directly. if ($contextParameter) { @@ -109,7 +109,7 @@ function Import-EditorCommand { $scriptBlock = [scriptblock]::Create($aCommand.Name) } - $editorCommand = New-Object Microsoft.PowerShell.EditorServices.Extensions.EditorCommand @( + $editorCommand = New-Object Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommand @( <# commandName: #> $details.Name, <# displayName: #> $details.DisplayName, <# suppressOutput: #> $details.SuppressOutput, diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psm1 b/module/PowerShellEditorServices/PowerShellEditorServices.psm1 index 47efd591f..b37b6ee65 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psm1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psm1 @@ -8,15 +8,9 @@ if ($PSEdition -eq 'Desktop') { Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Desktop/System.IO.Pipes.AccessControl.dll" Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Desktop/System.Security.AccessControl.dll" Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Desktop/System.Security.Principal.Windows.dll" -} elseif ($PSVersionTable.PSVersion -ge '6.0' -and $PSVersionTable.PSVersion -lt '6.1' -and $IsWindows) { - Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/6.0/System.IO.Pipes.AccessControl.dll" - Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/6.0/System.Security.AccessControl.dll" - Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/6.0/System.Security.Principal.Windows.dll" } Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.dll" -Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.Host.dll" -Microsoft.PowerShell.Utility\Add-Type -Path "$PSScriptRoot/bin/Microsoft.PowerShell.EditorServices.Protocol.dll" function Start-EditorServicesHost { [CmdletBinding()] @@ -97,13 +91,13 @@ function Start-EditorServicesHost { $editorServicesHost = $null $hostDetails = - Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Session.HostDetails @( + Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Hosting.HostDetails @( $HostName, $HostProfileId, (Microsoft.PowerShell.Utility\New-Object System.Version @($HostVersion))) $editorServicesHost = - Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Host.EditorServicesHost @( + Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Hosting.EditorServicesHost @( $hostDetails, $BundledModulesPath, $EnableConsoleRepl.IsPresent, @@ -114,7 +108,7 @@ function Start-EditorServicesHost { # Build the profile paths using the root paths of the current $profile variable $profilePaths = - Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Session.ProfilePaths @( + Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Hosting.ProfilePaths @( $hostDetails.ProfileId, [System.IO.Path]::GetDirectoryName($profile.AllUsersAllHosts), [System.IO.Path]::GetDirectoryName($profile.CurrentUserAllHosts)) @@ -122,32 +116,32 @@ function Start-EditorServicesHost { $editorServicesHost.StartLogging($LogPath, $LogLevel); $languageServiceConfig = - Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportConfig + Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportConfig $debugServiceConfig = - Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportConfig + Microsoft.PowerShell.Utility\New-Object Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportConfig switch ($PSCmdlet.ParameterSetName) { "Stdio" { - $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Stdio - $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Stdio + $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportType]::Stdio + $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportType]::Stdio break } "NamedPipe" { - $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe + $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportType]::NamedPipe $languageServiceConfig.InOutPipeName = "$LanguageServiceNamedPipe" if ($DebugServiceNamedPipe) { - $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe + $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportType]::NamedPipe $debugServiceConfig.InOutPipeName = "$DebugServiceNamedPipe" } break } "NamedPipeSimplex" { - $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe + $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportType]::NamedPipe $languageServiceConfig.InPipeName = $LanguageServiceInNamedPipe $languageServiceConfig.OutPipeName = $LanguageServiceOutNamedPipe if ($DebugServiceInNamedPipe -and $DebugServiceOutNamedPipe) { - $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe + $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Hosting.EditorServiceTransportType]::NamedPipe $debugServiceConfig.InPipeName = $DebugServiceInNamedPipe $debugServiceConfig.OutPipeName = $DebugServiceOutNamedPipe } diff --git a/module/docs/Import-EditorCommand.md b/module/docs/Import-EditorCommand.md index d84c66d53..cf90e8c55 100644 --- a/module/docs/Import-EditorCommand.md +++ b/module/docs/Import-EditorCommand.md @@ -30,7 +30,7 @@ The Import-EditorCommand function will search the specified module for functions Alternatively, you can specify command info objects (like those from the Get-Command cmdlet) to be processed directly. -To tag a command as an editor command, attach the attribute 'Microsoft.PowerShell.EditorServices.Extensions.EditorCommandAttribute' to the function like you would with 'CmdletBindingAttribute'. The attribute accepts the named parameters 'Name', 'DisplayName', and 'SuppressOutput'. +To tag a command as an editor command, attach the attribute 'Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommandAttribute' to the function like you would with 'CmdletBindingAttribute'. The attribute accepts the named parameters 'Name', 'DisplayName', and 'SuppressOutput'. ## EXAMPLES @@ -55,7 +55,7 @@ Registers all editor commands that contain "Editor" in the name and return all s ```powershell function Invoke-MyEditorCommand { [CmdletBinding()] - [Microsoft.PowerShell.EditorServices.Extensions.EditorCommand(DisplayName='My Command', SuppressOutput)] + [Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommand(DisplayName='My Command', SuppressOutput)] param() end { ConvertTo-ScriptExtent -Offset 0 | Set-ScriptExtent -Text 'My Command!' @@ -145,7 +145,7 @@ You can pass commands to register as editor commands. ## OUTPUTS -### Microsoft.PowerShell.EditorServices.Extensions.EditorCommand +### Microsoft.PowerShell.EditorServices.Services.PowerShellContext.EditorCommand If the "PassThru" parameter is specified editor commands that were successfully registered will be returned. This function does not output to the pipeline otherwise. diff --git a/scripts/azurePipelinesBuild.ps1 b/scripts/azurePipelinesBuild.ps1 index ab31ed358..774f57815 100644 --- a/scripts/azurePipelinesBuild.ps1 +++ b/scripts/azurePipelinesBuild.ps1 @@ -10,6 +10,9 @@ if ($IsWindows -or $PSVersionTable.PSVersion.Major -lt 6) { Import-Module -Name PackageManagement -MinimumVersion 1.1.7.0 -Force } +# Update help needed for SignatureHelp LSP request. +Update-Help -Force -ErrorAction SilentlyContinue + # Needed for build and docs gen. Install-Module InvokeBuild -MaximumVersion 5.1.0 -Scope CurrentUser Install-Module PlatyPS -RequiredVersion 0.9.0 -Scope CurrentUser 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/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 4a4acf9c6..000000000 --- a/src/PowerShellEditorServices.Host/EditorServicesHost.cs +++ /dev/null @@ -1,557 +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; - -#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/PowerShellEditorServices.Host.csproj b/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj deleted file mode 100644 index 020372b3f..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/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 be73b74a0..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 InitializeOptions { 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/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 3e781a217..000000000 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/WorkspaceClientCapabilities.cs +++ /dev/null @@ -1,47 +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' - /// /// - 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` - /// - 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/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 17c7da384..000000000 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs +++ /dev/null @@ -1,173 +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()) - { - try - { - // Send the message - await this.outputStream.WriteAsync(headerBytes, 0, headerBytes.Length); - await this.outputStream.WriteAsync(messageBytes, 0, messageBytes.Length); - await this.outputStream.FlushAsync(); - } - catch (Exception e) when ( - e is ObjectDisposedException || - e is IOException) - { - // We catch this exception for when the DebugAdapter disconnects while still processing a message. - logger.WriteHandledException("Tried to write to the output stream but it was already closed & broken.", e); - } - } - } - - 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/PowerShellEditorServices.Protocol.csproj b/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj deleted file mode 100644 index dee2bb843..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 f7b4854ae..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ /dev/null @@ -1,2090 +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) - { - if (this.editorSession.AnalysisService == null) - { - await requestContext.SendErrorAsync("Script analysis is not enabled in this session"); - return; - } - - 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) - { - if (this.editorSession.AnalysisService == null) - { - await requestContext.SendErrorAsync("Script analysis is not enabled in this session"); - return; - } - - 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 - - // Since we want to use a "tab stop" we need to escape a few things for Textmate to render properly. - var sb = new StringBuilder(completionDetails.CompletionText) - .Replace(@"\", @"\\") - .Replace(@"}", @"\}") - .Replace(@"$", @"\$"); - completionText = sb.Insert(sb.Length - 1, "$0").ToString(); - 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/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..de767821c --- /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.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..e1138cbe3 100644 --- a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj +++ b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj @@ -16,8 +16,9 @@ - - + + All + diff --git a/src/PowerShellEditorServices/.vscode/launch.json b/src/PowerShellEditorServices/.vscode/launch.json new file mode 100644 index 000000000..8b60d4fab --- /dev/null +++ b/src/PowerShellEditorServices/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "WARNING01": "*********************************************************************************", + "WARNING02": "The C# extension was unable to automatically to decode projects in the current", + "WARNING03": "workspace to create a runnable lanch.json file. A template launch.json file has", + "WARNING04": "been created as a placeholder.", + "WARNING05": "", + "WARNING06": "If OmniSharp is currently unable to load your project, you can attempt to resolve", + "WARNING07": "this by restoring any missing project dependencies (example: run 'dotnet restore')", + "WARNING08": "and by fixing any reported errors from building the projects in your workspace.", + "WARNING09": "If this allows OmniSharp to now load your project then --", + "WARNING10": " * Delete this file", + "WARNING11": " * Open the Visual Studio Code command palette (View->Command Palette)", + "WARNING12": " * run the command: '.NET: Generate Assets for Build and Debug'.", + "WARNING13": "", + "WARNING14": "If your project requires a more complex launch configuration, you may wish to delete", + "WARNING15": "this configuration and pick a different template using the 'Add Configuration...'", + "WARNING16": "button at the bottom of this file.", + "WARNING17": "*********************************************************************************", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//.dll", + "args": [], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file 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/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/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/Hosting/EditorServicesHost.cs b/src/PowerShellEditorServices/Hosting/EditorServicesHost.cs new file mode 100644 index 000000000..0f80f0920 --- /dev/null +++ b/src/PowerShellEditorServices/Hosting/EditorServicesHost.cs @@ -0,0 +1,501 @@ +// +// 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.IO.Pipes; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Server; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Utility; +using Serilog; + +namespace Microsoft.PowerShell.EditorServices.Hosting +{ + 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 + + // 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 HostDetails _hostDetails; + + private readonly PSHost _internalHost; + + private readonly bool _enableConsoleRepl; + + private readonly HashSet _featureFlags; + + private readonly string[] _additionalModules; + + private PsesLanguageServer _languageServer; + private PsesDebugServer _debugServer; + + private Microsoft.Extensions.Logging.ILogger _logger; + + private ILoggerFactory _factory; + + #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); + + _hostDetails = hostDetails; + + //this._hostDetails = hostDetails; + _enableConsoleRepl = enableConsoleRepl; + //this.bundledModulesPath = bundledModulesPath; + _additionalModules = additionalModules ?? Array.Empty(); + _featureFlags = new HashSet(featureFlags ?? Array.Empty()); + //this.serverCompletedTask = new TaskCompletionSource(); + _internalHost = internalHost; + +#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, PsesLogLevel logLevel) + { + Log.Logger = new LoggerConfiguration().Enrich.FromLogContext() + .WriteTo.File(logFilePath) + .CreateLogger(); + _factory = new LoggerFactory().AddSerilog(Log.Logger); + _logger = _factory.CreateLogger(); + + 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: {_hostDetails.Name} + Version: {_hostDetails.Version} + ProfileId: {_hostDetails.ProfileId} + Arch: {osArch} + + Operating system details: + + Version: {osVersion} + Arch: {osArch} + + Build information: + + Version: {BuildInfo.BuildVersion} + Origin: {BuildInfo.BuildOrigin} + Date: {buildTime} +"; + + _logger.LogInformation(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) + { + _logger.LogInformation($"LSP NamedPipe: {config.InOutPipeName}\nLSP OutPipe: {config.OutPipeName}"); + + switch (config.TransportType) + { + case EditorServiceTransportType.NamedPipe: + _languageServer = new NamedPipePsesLanguageServer( + _factory, + LogLevel.Trace, + _enableConsoleRepl, + _featureFlags, + _hostDetails, + _additionalModules, + _internalHost, + profilePaths, + config.InOutPipeName ?? config.InPipeName, + config.OutPipeName); + break; + + case EditorServiceTransportType.Stdio: + _languageServer = new StdioPsesLanguageServer( + _factory, + LogLevel.Trace, + _featureFlags, + _hostDetails, + _additionalModules, + _internalHost, + profilePaths); + break; + } + + _logger.LogInformation("Starting language server"); + + Task.Run(_languageServer.StartAsync); + + _logger.LogInformation( + string.Format( + "Language service started, type = {0}, endpoint = {1}", + config.TransportType, config.Endpoint)); + } + + + private bool alreadySubscribedDebug; + /// + /// 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) + { + _logger.LogInformation($"Debug NamedPipe: {config.InOutPipeName}\nDebug OutPipe: {config.OutPipeName}"); + + switch (config.TransportType) + { + case EditorServiceTransportType.NamedPipe: + NamedPipeServerStream inNamedPipe = CreateNamedPipe( + config.InOutPipeName ?? config.InPipeName, + config.OutPipeName, + out NamedPipeServerStream outNamedPipe); + + _debugServer = new PsesDebugServer( + _factory, + inNamedPipe, + outNamedPipe ?? inNamedPipe); + + Task[] tasks = outNamedPipe != null + ? new[] { inNamedPipe.WaitForConnectionAsync(), outNamedPipe.WaitForConnectionAsync() } + : new[] { inNamedPipe.WaitForConnectionAsync() }; + Task.WhenAll(tasks) + .ContinueWith(async task => + { + _logger.LogInformation("Starting debug server"); + await _debugServer.StartAsync(_languageServer.LanguageServer.Services); + _logger.LogInformation( + $"Debug service started, type = {config.TransportType}, endpoint = {config.Endpoint}"); + }); + + break; + + case EditorServiceTransportType.Stdio: + _debugServer = new PsesDebugServer( + _factory, + Console.OpenStandardInput(), + Console.OpenStandardOutput()); + + Task.Run(async () => + { + _logger.LogInformation("Starting debug server"); + + IServiceProvider serviceProvider = useExistingSession + ? _languageServer.LanguageServer.Services + : new ServiceCollection().AddSingleton( + (provider) => PowerShellContextService.Create( + _factory, + provider.GetService(), + profilePaths, + _featureFlags, + _enableConsoleRepl, + _internalHost, + _hostDetails, + _additionalModules)) + .BuildServiceProvider(); + + await _debugServer.StartAsync(serviceProvider); + _logger.LogInformation( + $"Debug service started, type = {config.TransportType}, endpoint = {config.Endpoint}"); + }); + break; + + default: + throw new NotSupportedException($"The transport {config.TransportType} is not supported"); + } + + if(!alreadySubscribedDebug) + { + alreadySubscribedDebug = true; + _debugServer.SessionEnded += (sender, eventArgs) => + { + _debugServer.Dispose(); + alreadySubscribedDebug = false; + StartDebugService(config, profilePaths, useExistingSession); + }; + } + } + + /// + /// Stops the language or debug services if either were started. + /// + public void StopServices() + { + // TODO: Need a new way to shut down the services + } + + /// + /// 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! + _languageServer.WaitForShutdown().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(); + } + } + + /// + /// 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(); + } + + private void CurrentDomain_UnhandledException( + object sender, + UnhandledExceptionEventArgs e) + { + // Log the exception + _logger.LogError($"FATAL UNHANDLED EXCEPTION: {e.ExceptionObject}"); + } + + private static NamedPipeServerStream CreateNamedPipe( + string inOutPipeName, + string outPipeName, + out NamedPipeServerStream outPipe) + { + // .NET Core implementation is simplest so try that first + if (VersionUtils.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 + + var 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 + }); + } + + #endregion + } +} diff --git a/src/PowerShellEditorServices/Session/HostDetails.cs b/src/PowerShellEditorServices/Hosting/HostDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Session/HostDetails.cs rename to src/PowerShellEditorServices/Hosting/HostDetails.cs index a20bc8cf3..cdbe96f94 100644 --- a/src/PowerShellEditorServices/Session/HostDetails.cs +++ b/src/PowerShellEditorServices/Hosting/HostDetails.cs @@ -1,11 +1,11 @@ -// +// // 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 +namespace Microsoft.PowerShell.EditorServices.Hosting { /// /// Contains details about the current host application (most diff --git a/src/PowerShellEditorServices/Session/ProfilePaths.cs b/src/PowerShellEditorServices/Hosting/ProfilePaths.cs similarity index 96% rename from src/PowerShellEditorServices/Session/ProfilePaths.cs rename to src/PowerShellEditorServices/Hosting/ProfilePaths.cs index ef94092ec..53eb714f1 100644 --- a/src/PowerShellEditorServices/Session/ProfilePaths.cs +++ b/src/PowerShellEditorServices/Hosting/ProfilePaths.cs @@ -3,13 +3,11 @@ // 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 +namespace Microsoft.PowerShell.EditorServices.Hosting { /// /// Provides profile path resolution behavior relative to the name diff --git a/src/PowerShellEditorServices/Hosting/PsesLogLevel.cs b/src/PowerShellEditorServices/Hosting/PsesLogLevel.cs new file mode 100644 index 000000000..d1b85077d --- /dev/null +++ b/src/PowerShellEditorServices/Hosting/PsesLogLevel.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.Extensions.Logging; + +namespace Microsoft.PowerShell.EditorServices.Hosting +{ + public enum PsesLogLevel + { + Diagnostic, + Verbose, + Normal, + Warning, + Error, + } + + internal static class PsesLogLevelExtensions + { + public static LogLevel ToExtensionsLogLevel(this PsesLogLevel logLevel) + { + switch (logLevel) + { + case PsesLogLevel.Diagnostic: + return LogLevel.Trace; + + case PsesLogLevel.Verbose: + return LogLevel.Debug; + + case PsesLogLevel.Normal: + return LogLevel.Information; + + case PsesLogLevel.Warning: + return LogLevel.Warning; + + case PsesLogLevel.Error: + return LogLevel.Error; + + default: + return LogLevel.Information; + } + } + } +} 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/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/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/Logging/LoggerExtensions.cs b/src/PowerShellEditorServices/Logging/LoggerExtensions.cs new file mode 100644 index 000000000..036f978a6 --- /dev/null +++ b/src/PowerShellEditorServices/Logging/LoggerExtensions.cs @@ -0,0 +1,36 @@ +// +// 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 Microsoft.Extensions.Logging; + +namespace Microsoft.PowerShell.EditorServices.Logging +{ + internal static class LoggerExtensions + { + public static void LogException( + this ILogger logger, + string message, + Exception exception, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = -1) + { + logger.LogError(message, exception); + } + + public static void LogHandledException( + this ILogger logger, + string message, + Exception exception, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = -1) + { + logger.LogError(message, exception); + } + } +} diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index f4b5be263..18b163544 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -1,5 +1,7 @@ - + + + PowerShell Editor Services Provides common PowerShell editor capabilities as a .NET library. @@ -7,24 +9,26 @@ Microsoft.PowerShell.EditorServices Latest - - - 1591,1573,1572 - bin\$(TargetFramework)\$(Configuration)\Microsoft.PowerShell.EditorServices.xml + + + latest + + + + + + latest - - - - - + + + + - - - - + + + + + - - $(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.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs b/src/PowerShellEditorServices/Server/NamedPipePsesLanguageServer.cs similarity index 53% rename from src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs rename to src/PowerShellEditorServices/Server/NamedPipePsesLanguageServer.cs index df79c806a..864373a30 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs +++ b/src/PowerShellEditorServices/Server/NamedPipePsesLanguageServer.cs @@ -3,21 +3,20 @@ // 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.Management.Automation.Host; using System.Reflection; -using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; -using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel +namespace Microsoft.PowerShell.EditorServices.Server { - public class NamedPipeServerListener : ServerListenerBase + internal class NamedPipePsesLanguageServer : PsesLanguageServer { // 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; @@ -26,82 +25,63 @@ public class NamedPipeServerListener : ServerListenerBase featureFlags, + HostDetails hostDetails, + string[] additionalModules, + PSHost internalHost, + ProfilePaths profilePaths, + string namedPipeName, + string outNamedPipeName) : base( + factory, + minimumLogLevel, + enableConsoleRepl, + featureFlags, + hostDetails, + additionalModules, + internalHost, + profilePaths) { - _logger = logger; - _inOutPipeName = inPipeName; - _outPipeName = outPipeName; + _namedPipeName = namedPipeName; + _outNamedPipeName = outNamedPipeName; } - public override void Start() + protected override (Stream input, Stream output) GetInputOutputStreams() { - 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); + NamedPipeServerStream namedPipe = CreateNamedPipe( + _namedPipeName, + _outNamedPipeName, + out NamedPipeServerStream outNamedPipe); - throw e; - } - } + var logger = LoggerFactory.CreateLogger("NamedPipeConnection"); - public override void Stop() - { - if (_inOutPipeServer != null) + logger.LogInformation("Waiting for connection"); + namedPipe.WaitForConnection(); + if (outNamedPipe != null) { - _logger.Write(LogLevel.Verbose, "Named pipe server shutting down..."); - - _inOutPipeServer.Dispose(); - - _logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); + outNamedPipe.WaitForConnection(); } - if (_outPipeServer != null) - { - _logger.Write(LogLevel.Verbose, $"Named out pipe server {_outPipeServer} shutting down..."); - - _outPipeServer.Dispose(); + logger.LogInformation("Connected"); - _logger.Write(LogLevel.Verbose, $"Named out pipe server {_outPipeServer} has been disposed."); - } + return (namedPipe, outNamedPipe ?? namedPipe); } - private static NamedPipeServerStream ConnectNamedPipe( + private static NamedPipeServerStream CreateNamedPipe( string inOutPipeName, string outPipeName, out NamedPipeServerStream outPipe) { // .NET Core implementation is simplest so try that first - if (Utils.IsNetCore) + if (VersionUtils.IsNetCore) { outPipe = outPipeName == null ? null @@ -123,7 +103,7 @@ private static NamedPipeServerStream ConnectNamedPipe( // Now deal with Windows PowerShell // We need to use reflection to get a nice constructor - PipeSecurity pipeSecurity = new PipeSecurity(); + var pipeSecurity = new PipeSecurity(); WindowsIdentity identity = WindowsIdentity.GetCurrent(); WindowsPrincipal principal = new WindowsPrincipal(identity); @@ -169,37 +149,5 @@ private static NamedPipeServerStream ConnectNamedPipe( 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/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs new file mode 100644 index 000000000..860dbc845 --- /dev/null +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -0,0 +1,111 @@ +// +// 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.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Server; + +namespace Microsoft.PowerShell.EditorServices.Server +{ + public class PsesDebugServer : IDisposable + { + protected readonly ILoggerFactory _loggerFactory; + private readonly Stream _inputStream; + private readonly Stream _outputStream; + + private IJsonRpcServer _jsonRpcServer; + + private PowerShellContextService _powerShellContextService; + + public PsesDebugServer( + ILoggerFactory factory, + Stream inputStream, + Stream outputStream) + { + _loggerFactory = factory; + _inputStream = inputStream; + _outputStream = outputStream; + } + + public async Task StartAsync(IServiceProvider languageServerServiceProvider) + { + _jsonRpcServer = await JsonRpcServer.From(options => + { + options.Serializer = new DapProtocolSerializer(); + options.Reciever = new DapReciever(); + options.LoggerFactory = _loggerFactory; + ILogger logger = options.LoggerFactory.CreateLogger("DebugOptionsStartup"); + + // We need to let the PowerShell Context Service know that we are in a debug session + // so that it doesn't send the powerShell/startDebugger message. + _powerShellContextService = languageServerServiceProvider.GetService(); + _powerShellContextService.IsDebugServerActive = true; + + options.Services = new ServiceCollection() + .AddSingleton(_powerShellContextService) + .AddSingleton(languageServerServiceProvider.GetService()) + .AddSingleton(languageServerServiceProvider.GetService()) + .AddSingleton(this) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + options + .WithInput(_inputStream) + .WithOutput(_outputStream); + + logger.LogInformation("Adding handlers"); + + options + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler(); + + logger.LogInformation("Handlers added"); + }); + } + + public void Dispose() + { + _powerShellContextService.IsDebugServerActive = false; + _jsonRpcServer.Dispose(); + } + + #region Events + + public event EventHandler SessionEnded; + + internal void OnSessionEnded() + { + SessionEnded?.Invoke(this, null); + } + + #endregion + } +} diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs new file mode 100644 index 000000000..64bb10faa --- /dev/null +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -0,0 +1,170 @@ +// +// 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.IO; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using OmniSharp.Extensions.LanguageServer.Server; + +namespace Microsoft.PowerShell.EditorServices.Server +{ + internal abstract class PsesLanguageServer + { + internal ILoggerFactory LoggerFactory { get; private set; } + internal ILanguageServer LanguageServer { get; private set; } + + private readonly LogLevel _minimumLogLevel; + private readonly bool _enableConsoleRepl; + private readonly HashSet _featureFlags; + private readonly HostDetails _hostDetails; + private readonly string[] _additionalModules; + private readonly PSHost _internalHost; + private readonly ProfilePaths _profilePaths; + private readonly TaskCompletionSource _serverStart; + + internal PsesLanguageServer( + ILoggerFactory factory, + LogLevel minimumLogLevel, + bool enableConsoleRepl, + HashSet featureFlags, + HostDetails hostDetails, + string[] additionalModules, + PSHost internalHost, + ProfilePaths profilePaths) + { + LoggerFactory = factory; + _minimumLogLevel = minimumLogLevel; + _enableConsoleRepl = enableConsoleRepl; + _featureFlags = featureFlags; + _hostDetails = hostDetails; + _additionalModules = additionalModules; + _internalHost = internalHost; + _profilePaths = profilePaths; + _serverStart = new TaskCompletionSource(); + } + + public async Task StartAsync() + { + LanguageServer = await OmniSharp.Extensions.LanguageServer.Server.LanguageServer.From(options => + { + options.AddDefaultLoggingProvider(); + options.LoggerFactory = LoggerFactory; + ILogger logger = options.LoggerFactory.CreateLogger("OptionsStartup"); + options.Services = new ServiceCollection() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton( + (provider) => + PowerShellContextService.Create( + LoggerFactory, + provider.GetService(), + _profilePaths, + _featureFlags, + _enableConsoleRepl, + _internalHost, + _hostDetails, + _additionalModules)) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton( + (provider) => + { + var extensionService = new ExtensionService( + provider.GetService(), + provider.GetService()); + extensionService.InitializeAsync( + serviceProvider: provider, + editorOperations: provider.GetService()) + .Wait(); + return extensionService; + }) + .AddSingleton( + (provider) => + { + return AnalysisService.Create( + provider.GetService(), + provider.GetService(), + options.LoggerFactory.CreateLogger()); + }); + + (Stream input, Stream output) = GetInputOutputStreams(); + + options + .WithInput(input) + .WithOutput(output); + + options.MinimumLogLevel = _minimumLogLevel; + + logger.LogInformation("Adding handlers"); + + options + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .WithHandler() + .OnInitialize( + async (languageServer, request) => + { + var serviceProvider = languageServer.Services; + var workspaceService = serviceProvider.GetService(); + + // Grab the workspace path from the parameters + workspaceService.WorkspacePath = request.RootPath; + + // Set the working directory of the PowerShell session to the workspace path + if (workspaceService.WorkspacePath != null + && Directory.Exists(workspaceService.WorkspacePath)) + { + await serviceProvider.GetService().SetWorkingDirectoryAsync( + workspaceService.WorkspacePath, + isPathAlreadyEscaped: false); + } + }); + + logger.LogInformation("Handlers added"); + }); + } + + public async Task WaitForShutdown() + { + await _serverStart.Task; + await LanguageServer.WaitForExit; + } + + protected abstract (Stream input, Stream output) GetInputOutputStreams(); + } +} diff --git a/src/PowerShellEditorServices/Server/StdioPsesLanguageServer.cs b/src/PowerShellEditorServices/Server/StdioPsesLanguageServer.cs new file mode 100644 index 000000000..a2c699b5e --- /dev/null +++ b/src/PowerShellEditorServices/Server/StdioPsesLanguageServer.cs @@ -0,0 +1,42 @@ +// +// 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.IO; +using System.Management.Automation.Host; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; + +namespace Microsoft.PowerShell.EditorServices.Server +{ + internal class StdioPsesLanguageServer : PsesLanguageServer + { + internal StdioPsesLanguageServer( + ILoggerFactory factory, + LogLevel minimumLogLevel, + HashSet featureFlags, + HostDetails hostDetails, + string[] additionalModules, + PSHost internalHost, + ProfilePaths profilePaths) : base( + factory, + minimumLogLevel, + // Stdio server can't support an integrated console so we pass in false. + false, + featureFlags, + hostDetails, + additionalModules, + internalHost, + profilePaths) + { + + } + + protected override (Stream input, Stream output) GetInputOutputStreams() + { + return (System.Console.OpenStandardInput(), System.Console.OpenStandardOutput()); + } + } +} diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs similarity index 66% rename from src/PowerShellEditorServices/Analysis/AnalysisService.cs rename to src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 045a50d31..a9ce58fe2 100644 --- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -3,20 +3,22 @@ // 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 +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System.Threading; +using System.Collections.Concurrent; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Provides a high-level service for performing semantic analysis @@ -54,6 +56,8 @@ public class AnalysisService : IDisposable private static readonly string[] s_emptyGetRuleResult = new string[0]; + private static CancellationTokenSource s_existingRequestCancellation; + /// /// The indentation to add when the logger lists errors. /// @@ -90,6 +94,12 @@ public class AnalysisService : IDisposable /// private PSModuleInfo _pssaModuleInfo; + private readonly ILanguageServer _languageServer; + + private readonly ConfigurationService _configurationService; + + private readonly ConcurrentDictionary)> _mostRecentCorrectionsByFile; + #endregion // Private Fields #region Properties @@ -129,14 +139,19 @@ private AnalysisService( RunspacePool analysisRunspacePool, string pssaSettingsPath, IEnumerable activeRules, + ILanguageServer languageServer, + ConfigurationService configurationService, ILogger logger, PSModuleInfo pssaModuleInfo = null) { _analysisRunspacePool = analysisRunspacePool; SettingsPath = pssaSettingsPath; ActiveRules = activeRules.ToArray(); + _languageServer = languageServer; + _configurationService = configurationService; _logger = logger; _pssaModuleInfo = pssaModuleInfo; + _mostRecentCorrectionsByFile = new ConcurrentDictionary)>(); } #endregion // constructors @@ -153,8 +168,9 @@ private AnalysisService( /// 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) + public static AnalysisService Create(ConfigurationService configurationService, ILanguageServer languageServer, ILogger logger) { + string settingsPath = configurationService.CurrentSettings.ScriptAnalysis.SettingsPath; try { RunspacePool analysisRunspacePool; @@ -187,6 +203,8 @@ public static AnalysisService Create(string settingsPath, ILogger logger) analysisRunspacePool, settingsPath, s_includedRules, + languageServer, + configurationService, logger, pssaModuleInfo); @@ -197,12 +215,12 @@ public static AnalysisService Create(string settingsPath, ILogger logger) } catch (AnalysisServiceLoadException e) { - logger.WriteException("PSScriptAnalyzer cannot be imported, AnalysisService will be disabled", e); + logger.LogWarning("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); + logger.LogWarning("AnalysisService could not be started due to an unexpected exception", e); return null; } } @@ -298,7 +316,7 @@ public IEnumerable GetPSScriptAnalyzerRules() PowerShellResult getRuleResult = InvokePowerShell("Get-ScriptAnalyzerRule"); if (getRuleResult == null) { - _logger.Write(LogLevel.Warning, "Get-ScriptAnalyzerRule returned null result"); + _logger.LogWarning("Get-ScriptAnalyzerRule returned null result"); return s_emptyGetRuleResult; } @@ -343,7 +361,7 @@ public async Task FormatAsync( if (result == null) { - _logger.Write(LogLevel.Error, "Formatter returned null result"); + _logger.LogError("Formatter returned null result"); return null; } @@ -354,7 +372,7 @@ public async Task FormatAsync( { errorBuilder.Append(err).Append(s_indentJoin); } - _logger.Write(LogLevel.Warning, $"Errors found while formatting file: {errorBuilder}"); + _logger.LogWarning($"Errors found while formatting file: {errorBuilder}"); return null; } @@ -418,8 +436,7 @@ private async Task> GetSemanticMarkersAsync( private void LogAvailablePssaFeatures() { // Save ourselves some work here - var featureLogLevel = LogLevel.Verbose; - if (_logger.MinimumConfiguredLogLevel > featureLogLevel) + if (!_logger.IsEnabled(LogLevel.Debug)) { return; } @@ -470,7 +487,7 @@ private void LogAvailablePssaFeatures() sb.AppendLine(ruleName); } - _logger.Write(featureLogLevel, sb.ToString()); + _logger.LogDebug(sb.ToString()); } private async Task GetDiagnosticRecordsAsync( @@ -517,9 +534,7 @@ private async Task GetDiagnosticRecordsAsync( diagnosticRecords = result?.Output; } - _logger.Write( - LogLevel.Verbose, - String.Format("Found {0} violations", diagnosticRecords.Count())); + _logger.LogDebug(String.Format("Found {0} violations", diagnosticRecords.Count())); return diagnosticRecords; } @@ -549,7 +564,7 @@ private PowerShellResult InvokePowerShell(string command, IDictionary cancelTask = new TaskCompletionSource(); + cancelTask.SetCanceled(); + return; + } + + // If filesToAnalzye is empty, nothing to do so return early. + if (filesToAnalyze.Length == 0) + { + return; + } + + // 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(); + await Task.Factory.StartNew( + () => + DelayThenInvokeDiagnosticsAsync( + 750, + filesToAnalyze, + _configurationService.CurrentSettings.ScriptAnalysis.Enable ?? false, + s_existingRequestCancellation.Token), + CancellationToken.None, + TaskCreationOptions.None, + TaskScheduler.Default); + } + + private async Task DelayThenInvokeDiagnosticsAsync( + int delayMilliseconds, + ScriptFile[] filesToAnalyze, + bool isScriptAnalysisEnabled, + 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) + { + PublishScriptDiagnostics( + script, + script.DiagnosticMarkers); + } + + 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) + { + semanticMarkers = await GetSemanticMarkersAsync(scriptFile); + } + else + { + // Semantic markers aren't available if the AnalysisService + // isn't available + semanticMarkers = new List(); + } + + scriptFile.DiagnosticMarkers.AddRange(semanticMarkers); + + PublishScriptDiagnostics( + scriptFile, + // Concat script analysis errors to any existing parse errors + scriptFile.DiagnosticMarkers); + } + } + + internal void ClearMarkers(ScriptFile scriptFile) + { + // send empty diagnostic markers to clear any markers associated with the given file + PublishScriptDiagnostics( + scriptFile, + new List()); + } + + private void PublishScriptDiagnostics( + ScriptFile scriptFile, + List markers) + { + var diagnostics = new List(); + + // Create the entry for this file if it does not already exist + SemaphoreSlim fileLock; + Dictionary fileCorrections; + bool newEntryNeeded = false; + if (_mostRecentCorrectionsByFile.TryGetValue(scriptFile.DocumentUri, out (SemaphoreSlim, Dictionary) fileCorrectionsEntry)) + { + fileLock = fileCorrectionsEntry.Item1; + fileCorrections = fileCorrectionsEntry.Item2; + } + else + { + fileLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); + fileCorrections = new Dictionary(); + newEntryNeeded = true; + } + + fileLock.Wait(); + try + { + if (newEntryNeeded) + { + // If we create a new entry, we should do it after acquiring the lock we just created + // to ensure a competing thread can never acquire it first and read invalid information from it + _mostRecentCorrectionsByFile[scriptFile.DocumentUri] = (fileLock, fileCorrections); + } + else + { + // Otherwise we need to clear the stale corrections + fileCorrections.Clear(); + } + + foreach (ScriptFileMarker marker in markers) + { + // Does the marker contain a correction? + Diagnostic markerDiagnostic = GetDiagnosticFromMarker(marker); + if (marker.Correction != null) + { + string diagnosticId = GetUniqueIdFromDiagnostic(markerDiagnostic); + fileCorrections[diagnosticId] = marker.Correction; + } + + diagnostics.Add(markerDiagnostic); + } + } + finally + { + fileLock.Release(); + } + + + var uriBuilder = new UriBuilder + { + Scheme = Uri.UriSchemeFile, + Path = scriptFile.FilePath, + Host = string.Empty, + }; + + // Always send syntax and semantic errors. We want to + // make sure no out-of-date markers are being displayed. + _languageServer.Document.PublishDiagnostics(new PublishDiagnosticsParams() + { + Uri = uriBuilder.Uri, + Diagnostics = new Container(diagnostics), + }); + } + + public async Task> GetMostRecentCodeActionsForFileAsync(string documentUri) + { + if (!_mostRecentCorrectionsByFile.TryGetValue(documentUri, out (SemaphoreSlim fileLock, Dictionary corrections) fileCorrectionsEntry)) + { + return null; + } + + await fileCorrectionsEntry.fileLock.WaitAsync(); + // We must copy the dictionary for thread safety + var corrections = new Dictionary(fileCorrectionsEntry.corrections.Count); + try + { + foreach (KeyValuePair correction in fileCorrectionsEntry.corrections) + { + corrections.Add(correction.Key, correction.Value); + } + + return corrections; + } + finally + { + fileCorrectionsEntry.fileLock.Release(); + } + } + + // 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. + internal 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.IsString ? diagnostic.Code.String : diagnostic.Code.Long.ToString()) + .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 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; + } + } } /// diff --git a/src/PowerShellEditorServices/Services/CodeLens/CodeLensData.cs b/src/PowerShellEditorServices/Services/CodeLens/CodeLensData.cs new file mode 100644 index 000000000..15fff728d --- /dev/null +++ b/src/PowerShellEditorServices/Services/CodeLens/CodeLensData.cs @@ -0,0 +1,17 @@ +// +// 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.CodeLenses +{ + /// + /// Represents data expected back in an LSP CodeLens response. + /// + internal class CodeLensData + { + public string Uri { get; set; } + + public string ProviderId { get; set; } + } +} diff --git a/src/PowerShellEditorServices/CodeLenses/ICodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs similarity index 69% rename from src/PowerShellEditorServices/CodeLenses/ICodeLensProvider.cs rename to src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs index 71a1c0243..f42525a19 100644 --- a/src/PowerShellEditorServices/CodeLenses/ICodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs @@ -3,19 +3,22 @@ // 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; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace Microsoft.PowerShell.EditorServices.CodeLenses { /// /// Specifies the contract for a Code Lens provider. /// - public interface ICodeLensProvider : IFeatureProvider + public interface ICodeLensProvider { + /// + /// Specifies a unique identifier for the feature provider, typically a + /// fully-qualified name like "Microsoft.PowerShell.EditorServices.MyProvider" + /// + string ProviderId { get; } + /// /// Provides a collection of CodeLenses for the given /// document. @@ -32,15 +35,15 @@ public interface ICodeLensProvider : IFeatureProvider /// /// 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 ResolveCodeLens( CodeLens codeLens, - CancellationToken cancellationToken); + ScriptFile scriptFile); } } diff --git a/src/PowerShellEditorServices/CodeLenses/ICodeLenses.cs b/src/PowerShellEditorServices/Services/CodeLens/ICodeLenses.cs similarity index 84% rename from src/PowerShellEditorServices/CodeLenses/ICodeLenses.cs rename to src/PowerShellEditorServices/Services/CodeLens/ICodeLenses.cs index f63319c1c..fd6e1eadf 100644 --- a/src/PowerShellEditorServices/CodeLenses/ICodeLenses.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ICodeLenses.cs @@ -4,8 +4,8 @@ // using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace Microsoft.PowerShell.EditorServices.CodeLenses { @@ -19,7 +19,7 @@ public interface ICodeLenses /// Gets the collection of ICodeLensProvider implementations /// that are registered with this component. /// - IFeatureProviderCollection Providers { get; } + List Providers { get; } /// /// Provides a collection of CodeLenses for the given diff --git a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs similarity index 58% rename from src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs rename to src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index cc4a706b0..d927dd90e 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -4,32 +4,33 @@ // using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Commands; -using Microsoft.PowerShell.EditorServices.Symbols; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace Microsoft.PowerShell.EditorServices.CodeLenses { - internal class PesterCodeLensProvider : FeatureProviderBase, ICodeLensProvider + internal class PesterCodeLensProvider : ICodeLensProvider { + /// - /// The editor session context to provide CodeLenses for. + /// The symbol provider to get symbols from to build code lenses with. /// - private EditorSession _editorSession; + private readonly IDocumentSymbolProvider _symbolProvider; /// - /// The symbol provider to get symbols from to build code lenses with. + /// Specifies a unique identifier for the feature provider, typically a + /// fully-qualified name like "Microsoft.PowerShell.EditorServices.MyProvider" /// - private IDocumentSymbolProvider _symbolProvider; + public string ProviderId => nameof(PesterCodeLensProvider); /// /// 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) + public PesterCodeLensProvider() { - _editorSession = editorSession; _symbolProvider = new PesterDocumentSymbolProvider(); } @@ -41,33 +42,46 @@ public PesterCodeLensProvider(EditorSession editorSession) /// 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[] { + new CodeLens() + { + Range = pesterSymbol.ScriptRegion.ToRange(), + Data = JToken.FromObject(new { + Uri = scriptFile.DocumentUri, + ProviderId = nameof(PesterCodeLensProvider) + }), + Command = new Command() + { + Name = "PowerShell.RunPesterTests", + Title = "Run tests", + Arguments = JArray.FromObject(new object[] { scriptFile.DocumentUri, false /* No debug */, pesterSymbol.TestName, - pesterSymbol.ScriptRegion?.StartLineNumber })), + pesterSymbol.ScriptRegion?.StartLineNumber }) + } + }, - new CodeLens( - this, - scriptFile, - pesterSymbol.ScriptRegion, - new ClientCommand( - "PowerShell.RunPesterTests", - "Debug tests", - new object[] { + new CodeLens() + { + Range = pesterSymbol.ScriptRegion.ToRange(), + Data = JToken.FromObject(new { + Uri = scriptFile.DocumentUri, + ProviderId = nameof(PesterCodeLensProvider) + }), + Command = new Command() + { + Name = "PowerShell.RunPesterTests", + Title = "Debug tests", + Arguments = JArray.FromObject(new object[] { scriptFile.DocumentUri, - true /* Run in the debugger */, + true /* No debug */, pesterSymbol.TestName, - pesterSymbol.ScriptRegion?.StartLineNumber })), + pesterSymbol.ScriptRegion?.StartLineNumber }) + } + } }; return codeLensResults; @@ -101,13 +115,13 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) /// Resolve the CodeLens provision asynchronously -- just wraps the CodeLens argument in a task. /// /// The code lens to resolve. - /// + /// The script file. /// The given CodeLens, wrapped in a task. - public Task ResolveCodeLensAsync(CodeLens codeLens, CancellationToken cancellationToken) + public CodeLens ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile) { // This provider has no specific behavior for // resolving CodeLenses. - return Task.FromResult(codeLens); + return codeLens; } } } diff --git a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs similarity index 62% rename from src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs rename to src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 69edb6cc3..5d1a55ae6 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -5,44 +5,48 @@ 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; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace Microsoft.PowerShell.EditorServices.CodeLenses { /// /// Provides the "reference" code lens by extracting document symbols. /// - internal class ReferencesCodeLensProvider : FeatureProviderBase, ICodeLensProvider + internal class ReferencesCodeLensProvider : ICodeLensProvider { private static readonly Location[] s_emptyLocationArray = new Location[0]; /// - /// The editor session code lenses are being provided from. + /// The document symbol provider to supply symbols to generate the code lenses. /// - private EditorSession _editorSession; + private IDocumentSymbolProvider _symbolProvider; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; /// - /// The document symbol provider to supply symbols to generate the code lenses. + /// Specifies a unique identifier for the feature provider, typically a + /// fully-qualified name like "Microsoft.PowerShell.EditorServices.MyProvider" /// - private IDocumentSymbolProvider _symbolProvider; + public string ProviderId => nameof(ReferencesCodeLensProvider); /// /// Construct a new ReferencesCodeLensProvider for a given EditorSession. /// - /// - public ReferencesCodeLensProvider(EditorSession editorSession) + /// + /// + public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsService symbolsService) { - _editorSession = editorSession; - + _workspaceService = workspaceService; + _symbolsService = symbolsService; // TODO: Pull this from components _symbolProvider = new ScriptDocumentSymbolProvider( - editorSession.PowerShellContext.LocalPowerShellVersion.Version); + VersionUtils.PSVersion); } /// @@ -57,7 +61,15 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) { if (sym.SymbolType == SymbolType.Function) { - acc.Add(new CodeLens(this, scriptFile, sym.ScriptRegion)); + acc.Add(new CodeLens + { + Data = JToken.FromObject(new + { + Uri = scriptFile.DocumentUri, + ProviderId = nameof(ReferencesCodeLensProvider) + }), + Range = sym.ScriptRegion.ToRange() + }); } } @@ -68,24 +80,22 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) /// 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) + public CodeLens ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile) { - ScriptFile[] references = _editorSession.Workspace.ExpandScriptReferences( - codeLens.File); - SymbolReference foundSymbol = _editorSession.LanguageService.FindFunctionDefinitionAtLocation( - codeLens.File, - codeLens.ScriptExtent.StartLineNumber, - codeLens.ScriptExtent.StartColumnNumber); + ScriptFile[] references = _workspaceService.ExpandScriptReferences( + scriptFile); - FindReferencesResult referencesResult = await _editorSession.LanguageService.FindReferencesOfSymbolAsync( + SymbolReference foundSymbol = _symbolsService.FindFunctionDefinitionAtLocation( + scriptFile, + (int)codeLens.Range.Start.Line + 1, + (int)codeLens.Range.Start.Character + 1); + + List referencesResult = _symbolsService.FindReferencesOfSymbol( foundSymbol, references, - _editorSession.Workspace); + _workspaceService); Location[] referenceLocations; if (referencesResult == null) @@ -95,7 +105,7 @@ public async Task ResolveCodeLensAsync( else { var acc = new List(); - foreach (SymbolReference foundReference in referencesResult.FoundReferences) + foreach (SymbolReference foundReference in referencesResult) { if (!NotReferenceDefinition(foundSymbol, foundReference)) { @@ -104,25 +114,29 @@ public async Task ResolveCodeLensAsync( acc.Add(new Location { - Uri = GetFileUri(foundReference.FilePath), + Uri = PathUtils.ToUri(foundReference.FilePath), Range = foundReference.ScriptRegion.ToRange() }); } referenceLocations = acc.ToArray(); } - return new CodeLens( - codeLens, - new ClientCommand( - "editor.action.showReferences", - GetReferenceCountHeader(referenceLocations.Length), - new object[] + return new CodeLens + { + Data = codeLens.Data, + Range = codeLens.Range, + Command = new Command + { + Name = "editor.action.showReferences", + Title = GetReferenceCountHeader(referenceLocations.Length), + Arguments = JArray.FromObject(new object[] { - codeLens.File.DocumentUri, - codeLens.ScriptExtent.ToRange().Start, - referenceLocations, - } - )); + scriptFile.DocumentUri, + codeLens.Range.Start, + referenceLocations + }) + } + }; } /// @@ -141,20 +155,6 @@ private static bool NotReferenceDefinition( || !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. diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs new file mode 100644 index 000000000..da867637e --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -0,0 +1,177 @@ +// +// 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.DebugAdapter.Protocol.Events; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Services +{ + internal class DebugEventHandlerService + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + private readonly DebugService _debugService; + private readonly DebugStateService _debugStateService; + private readonly IJsonRpcServer _jsonRpcServer; + + public DebugEventHandlerService( + ILoggerFactory factory, + PowerShellContextService powerShellContextService, + DebugService debugService, + DebugStateService debugStateService, + IJsonRpcServer jsonRpcServer) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + _debugService = debugService; + _debugStateService = debugStateService; + _jsonRpcServer = jsonRpcServer; + } + + internal void RegisterEventHandlers() + { + _powerShellContextService.RunspaceChanged += PowerShellContext_RunspaceChanged; + _debugService.BreakpointUpdated += DebugService_BreakpointUpdated; + _debugService.DebuggerStopped += DebugService_DebuggerStopped; + _powerShellContextService.DebuggerResumed += PowerShellContext_DebuggerResumed; + } + + internal void UnregisterEventHandlers() + { + _powerShellContextService.RunspaceChanged -= PowerShellContext_RunspaceChanged; + _debugService.BreakpointUpdated -= DebugService_BreakpointUpdated; + _debugService.DebuggerStopped -= DebugService_DebuggerStopped; + _powerShellContextService.DebuggerResumed -= PowerShellContext_DebuggerResumed; + } + + #region Public methods + + internal void TriggerDebuggerStopped(DebuggerStoppedEventArgs e) + { + DebugService_DebuggerStopped(null, e); + } + + #endregion + + #region Event Handlers + + private void DebugService_DebuggerStopped(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"; + } + + _jsonRpcServer.SendNotification(EventNames.Stopped, + new StoppedEvent + { + ThreadId = 1, + Reason = debuggerStoppedReason + }); + } + + private void PowerShellContext_RunspaceChanged(object sender, RunspaceChangedEventArgs e) + { + if (_debugStateService.WaitingForAttach && + e.ChangeAction == RunspaceChangeAction.Enter && + e.NewRunspace.Context == RunspaceContext.DebuggedRunspace) + { + // Send the InitializedEvent so that the debugger will continue + // sending configuration requests + _debugStateService.WaitingForAttach = false; + _jsonRpcServer.SendNotification(EventNames.Initialized); + } + else if ( + e.ChangeAction == RunspaceChangeAction.Exit && + _powerShellContextService.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 + _jsonRpcServer.SendNotification(EventNames.Continued, + new ContinuedEvent + { + ThreadId = 1, + AllThreadsContinued = true + }); + } + } + + private void PowerShellContext_DebuggerResumed(object sender, DebuggerResumeAction e) + { + _jsonRpcServer.SendNotification(EventNames.Continued, + new ContinuedEvent + { + AllThreadsContinued = true, + ThreadId = 1 + }); + } + + private void DebugService_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) + { + string reason = "changed"; + + if (_debugStateService.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; + } + + OmniSharp.Extensions.DebugAdapter.Protocol.Models.Breakpoint breakpoint; + if (e.Breakpoint is LineBreakpoint) + { + breakpoint = LspDebugUtils.CreateBreakpoint(BreakpointDetails.Create(e.Breakpoint)); + } + else if (e.Breakpoint is CommandBreakpoint) + { + _logger.LogTrace("Function breakpoint updated event is not supported yet"); + return; + } + else + { + _logger.LogError($"Unrecognized breakpoint type {e.Breakpoint.GetType().FullName}"); + return; + } + + breakpoint.Verified = e.UpdateType != BreakpointUpdateType.Disabled; + + _jsonRpcServer.SendNotification(EventNames.Breakpoint, + new BreakpointEvent + { + Reason = reason, + Breakpoint = breakpoint + }); + } + + #endregion + } +} diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs similarity index 92% rename from src/PowerShellEditorServices/Debugging/DebugService.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 98d47d7af..dbde29254 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -11,31 +11,33 @@ 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Provides a high-level service for interacting with the /// PowerShell debugger in the runspace managed by a PowerShellContext. /// - public class DebugService + internal 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; + private readonly ILogger logger; + private readonly PowerShellContextService powerShellContext; + private RemoteFileManagerService remoteFileManager; // TODO: This needs to be managed per nested session - private Dictionary> breakpointsPerFile = + private readonly Dictionary> breakpointsPerFile = new Dictionary>(); private int nextVariableId; @@ -44,11 +46,11 @@ public class DebugService private VariableContainerDetails globalScopeVariables; private VariableContainerDetails scriptScopeVariables; private StackFrameDetails[] stackFrameDetails; - private PropertyInfo invocationTypeScriptPositionProperty; + private readonly PropertyInfo invocationTypeScriptPositionProperty; - private static int breakpointHitCounter = 0; + private static int breakpointHitCounter; - private SemaphoreSlim debugInfoHandle = AsyncUtils.CreateSimpleLockingSemaphore(); + private readonly SemaphoreSlim debugInfoHandle = AsyncUtils.CreateSimpleLockingSemaphore(); #endregion #region Properties @@ -83,10 +85,10 @@ public class DebugService /// 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) - { - } + //public DebugService(PowerShellContextService powerShellContext, ILogger logger) + // : this(powerShellContext, null, logger) + //{ + //} /// /// Initializes a new instance of the DebugService class and uses @@ -95,18 +97,18 @@ public DebugService(PowerShellContext powerShellContext, ILogger logger) /// /// The PowerShellContext to use for all debugging operations. /// - /// - /// A RemoteFileManager instance to use for accessing files in remote sessions. - /// + //// + //// A RemoteFileManagerService 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) + PowerShellContextService powerShellContext, + RemoteFileManagerService remoteFileManager, + ILoggerFactory factory) { Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - this.logger = logger; + this.logger = factory.CreateLogger(); this.powerShellContext = powerShellContext; this.powerShellContext.DebuggerStop += this.OnDebuggerStopAsync; this.powerShellContext.DebuggerResumed += this.OnDebuggerResumed; @@ -145,15 +147,14 @@ public async Task SetLineBreakpointsAsync( .CurrentRunspace .GetCapability(); - // Make sure we're using the remote script path string scriptPath = scriptFile.FilePath; + // Make sure we're using the remote script path if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && this.remoteFileManager != null) { if (!this.remoteFileManager.IsUnderRemoteTempPath(scriptPath)) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( $"Could not set breakpoints for local path '{scriptPath}' in a remote session."); return resultBreakpointDetails.ToArray(); @@ -170,8 +171,7 @@ public async Task SetLineBreakpointsAsync( this.temporaryScriptListingPath != null && this.temporaryScriptListingPath.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase)) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( $"Could not set breakpoint on temporary script listing path '{scriptPath}'."); return resultBreakpointDetails.ToArray(); @@ -180,7 +180,7 @@ public async Task SetLineBreakpointsAsync( // 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); + PowerShellContextService.WildcardEscapePath(scriptPath); if (dscBreakpoints == null || !dscBreakpoints.IsDscResourcePath(escapedScriptPath)) { @@ -369,7 +369,7 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) { if ((variableReferenceId < 0) || (variableReferenceId >= this.variables.Count)) { - logger.Write(LogLevel.Warning, $"Received request for variableReferenceId {variableReferenceId} that is out of range of valid indices."); + logger.LogWarning($"Received request for variableReferenceId {variableReferenceId} that is out of range of valid indices."); return new VariableDetailsBase[0]; } @@ -475,7 +475,7 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str 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}'"); + this.logger.LogTrace($"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) @@ -503,8 +503,7 @@ await this.powerShellContext.ExecuteCommandAsync( // 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) + if (psobject is ErrorRecord errorRecord) { throw new InvalidPowerShellExpressionException(errorRecord.ToString()); } @@ -598,7 +597,7 @@ await this.powerShellContext.ExecuteCommandAsync( EngineIntrinsics executionContext = getExecContextResults.OfType().FirstOrDefault(); var msg = $"Setting variable '{name}' using conversion to value: {psobject ?? ""}"; - this.logger.Write(LogLevel.Verbose, msg); + this.logger.LogTrace(msg); psVariable.Value = argTypeConverterAttr.Transform(executionContext, psobject); } @@ -606,14 +605,14 @@ await this.powerShellContext.ExecuteCommandAsync( { // 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); + this.logger.LogTrace(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 ?? ""}"); + this.logger.LogTrace($"Set variable '{name}' to: {tempVariable.ValueString ?? ""}"); return tempVariable.ValueString; } @@ -743,11 +742,18 @@ public VariableScope[] GetVariableScopes(int stackFrameId) /// public async Task ClearAllBreakpointsAsync() { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); + try + { + PSCommand psCommand = new PSCommand(); + psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); + psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); - await this.powerShellContext.ExecuteCommandAsync(psCommand); + await this.powerShellContext.ExecuteCommandAsync(psCommand); + } + catch (Exception e) + { + logger.LogException("Caught exception while clearing breakpoints from session", e); + } } #endregion @@ -756,10 +762,8 @@ public async Task ClearAllBreakpointsAsync() 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 (this.breakpointsPerFile.TryGetValue(scriptFile.Id, out List breakpoints)) { if (breakpoints.Count > 0) { @@ -791,10 +795,12 @@ private async Task FetchStackFramesAndVariablesAsync(string scriptNameOverride) try { this.nextVariableId = VariableDetailsBase.FirstVariableId; - this.variables = new List(); + this.variables = new List + { - // Create a dummy variable for index 0, should never see this. - this.variables.Add(new VariableDetails("Dummy", null)); + // Create a dummy variable for index 0, should never see this. + new VariableDetails("Dummy", null) + }; // Must retrieve global/script variales before stack frame variables // as we check stack frame variables against globals. @@ -877,8 +883,7 @@ private bool AddToAutoVariables(PSObject psvariable, string scope) optionsProperty.Value as string, out variableScope)) { - this.logger.Write( - LogLevel.Warning, + this.logger.LogWarning( $"Could not parse a variable's ScopedItemOptions value of '{optionsProperty.Value}'"); } } @@ -896,8 +901,8 @@ private bool AddToAutoVariables(PSObject psvariable, string scope) } else if (variableName.Equals("args", StringComparison.OrdinalIgnoreCase)) { - var array = variableValue as Array; - return array != null ? array.Length > 0 : false; + return variableValue is Array array + && array.Length > 0; } return false; @@ -996,9 +1001,7 @@ private ScriptBlock GetBreakpointActionScriptBlock( // If HitCondition specified, parse and verify it. if (!(String.IsNullOrWhiteSpace(breakpoint.HitCondition))) { - int parsedHitCount; - - if (Int32.TryParse(breakpoint.HitCondition, out parsedHitCount)) + if (Int32.TryParse(breakpoint.HitCondition, out int parsedHitCount)) { hitCount = parsedHitCount; } @@ -1012,22 +1015,21 @@ private ScriptBlock GetBreakpointActionScriptBlock( } // Create an Action scriptblock based on condition and/or hit count passed in. - if (hitCount.HasValue && String.IsNullOrWhiteSpace(breakpoint.Condition)) + 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)) + 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)) + if (!ValidateBreakpointConditionAst(actionScriptBlock.Ast, out string message)) { breakpoint.Verified = false; breakpoint.Message = message; @@ -1068,7 +1070,7 @@ private ScriptBlock GetBreakpointActionScriptBlock( { // 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."); + this.logger.LogWarning("No condition and no hit count specified by caller."); } return actionScriptBlock; @@ -1088,12 +1090,11 @@ 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)) + if (conditionAst is ScriptBlockAst scriptBlockAst && + 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; @@ -1104,9 +1105,9 @@ private bool ValidateBreakpointConditionAst(Ast conditionAst, out string message return false; } - PipelineAst pipelineAst = statementAst as PipelineAst; - if ((pipelineAst != null) && (pipelineAst.PipelineElements.Count == 1) && - (pipelineAst.PipelineElements[0].Redirections.Count > 0)) + if (statementAst is PipelineAst pipelineAst + && pipelineAst.PipelineElements.Count == 1 + && pipelineAst.PipelineElements[0].Redirections.Count > 0) { message = FormatInvalidBreakpointConditionMessage(condition, "Use '-gt' instead of '>'."); return false; @@ -1190,7 +1191,7 @@ private string TrimScriptListingLine(PSObject scriptLineObj, ref int prefixLengt /// public event EventHandler DebuggerStopped; - private async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e) + internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e) { bool noScriptName = false; string localScriptPath = e.InvocationInfo.ScriptName; @@ -1231,9 +1232,7 @@ await this.powerShellContext.ExecuteCommandAsync( } else { - this.logger.Write( - LogLevel.Warning, - $"Could not load script context"); + this.logger.LogWarning($"Could not load script context"); } } @@ -1256,11 +1255,9 @@ await this.remoteFileManager.FetchRemoteFileAsync( 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) + if (this.invocationTypeScriptPositionProperty + .GetValue(e.InvocationInfo) is IScriptExtent scriptExtent) { this.stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber; this.stackFrameDetails[0].EndLineNumber = scriptExtent.EndLineNumber; @@ -1298,11 +1295,8 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) // 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) + if (e.Breakpoint is LineBreakpoint lineBreakpoint) { - List breakpoints; - string scriptPath = lineBreakpoint.Script; if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && this.remoteFileManager != null) @@ -1314,8 +1308,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) if (mappedPath == null) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( $"Could not map remote path '{scriptPath}' to a local path."); return; @@ -1328,7 +1321,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) string normalizedScriptName = scriptPath.ToLower(); // Get the list of breakpoints for this file - if (!this.breakpointsPerFile.TryGetValue(normalizedScriptName, out breakpoints)) + if (!this.breakpointsPerFile.TryGetValue(normalizedScriptName, out List breakpoints)) { breakpoints = new List(); this.breakpointsPerFile.Add( diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs new file mode 100644 index 000000000..c5b755358 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs @@ -0,0 +1,32 @@ +// +// 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.Services +{ + internal class DebugStateService + { + internal bool NoDebug { get; set; } + + internal string Arguments { get; set; } + + internal bool IsRemoteAttach { get; set; } + + internal bool IsAttachSession { get; set; } + + internal bool WaitingForAttach { get; set; } + + internal string ScriptToLaunch { get; set; } + + internal bool OwnsEditorSession { get; set; } + + internal bool ExecutionCompleted { get; set; } + + internal bool IsInteractiveDebugSession { get; set; } + + internal bool SetBreakpointInProgress { get; set; } + + internal bool IsUsingTempIntegratedConsole { get; set; } + } +} diff --git a/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetails.cs similarity index 95% rename from src/PowerShellEditorServices/Debugging/BreakpointDetails.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetails.cs index f89606a2a..4e4ee6340 100644 --- a/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetails.cs @@ -7,7 +7,7 @@ using System.Management.Automation; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Provides details about a breakpoint that is set in the @@ -80,8 +80,7 @@ public static BreakpointDetails Create(Breakpoint breakpoint) { Validate.IsNotNull("breakpoint", breakpoint); - LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint; - if (lineBreakpoint == null) + if (!(breakpoint is LineBreakpoint lineBreakpoint)) { throw new ArgumentException( "Unexpected breakpoint type: " + breakpoint.GetType().Name); diff --git a/src/PowerShellEditorServices/Debugging/BreakpointDetailsBase.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetailsBase.cs similarity index 84% rename from src/PowerShellEditorServices/Debugging/BreakpointDetailsBase.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetailsBase.cs index 9fa83c23e..061939836 100644 --- a/src/PowerShellEditorServices/Debugging/BreakpointDetailsBase.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetailsBase.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Provides details about a breakpoint that is set in the @@ -12,13 +12,13 @@ namespace Microsoft.PowerShell.EditorServices public abstract class BreakpointDetailsBase { /// - /// Gets or sets a boolean indicator that if true, breakpoint could be set - /// (but not necessarily at the desired location). + /// 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 + /// 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; } diff --git a/src/PowerShellEditorServices/Debugging/CommandBreakpointDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/CommandBreakpointDetails.cs similarity index 93% rename from src/PowerShellEditorServices/Debugging/CommandBreakpointDetails.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/CommandBreakpointDetails.cs index 8197480c9..2b7c65def 100644 --- a/src/PowerShellEditorServices/Debugging/CommandBreakpointDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/CommandBreakpointDetails.cs @@ -7,7 +7,7 @@ using System.Management.Automation; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Provides details about a command breakpoint that is set in the PowerShell debugger. @@ -54,8 +54,7 @@ public static CommandBreakpointDetails Create(Breakpoint breakpoint) { Validate.IsNotNull("breakpoint", breakpoint); - CommandBreakpoint commandBreakpoint = breakpoint as CommandBreakpoint; - if (commandBreakpoint == null) + if (!(breakpoint is CommandBreakpoint commandBreakpoint)) { throw new ArgumentException( "Unexpected breakpoint type: " + breakpoint.GetType().Name); diff --git a/src/PowerShellEditorServices/Debugging/DebuggerStoppedEventArgs.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs similarity index 96% rename from src/PowerShellEditorServices/Debugging/DebuggerStoppedEventArgs.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs index f53ab8323..26ea3519a 100644 --- a/src/PowerShellEditorServices/Debugging/DebuggerStoppedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs @@ -3,11 +3,11 @@ // 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.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using System.Management.Automation; -namespace Microsoft.PowerShell.EditorServices.Debugging +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Provides event arguments for the DebugService.DebuggerStopped event. diff --git a/src/PowerShellEditorServices/Debugging/InvalidPowerShellExpressionException.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs similarity index 91% rename from src/PowerShellEditorServices/Debugging/InvalidPowerShellExpressionException.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs index fd6b13caf..82597bdd4 100644 --- a/src/PowerShellEditorServices/Debugging/InvalidPowerShellExpressionException.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs @@ -5,7 +5,7 @@ using System; -namespace Microsoft.PowerShell.EditorServices.Debugging +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Represents the exception that is thrown when an invalid expression is provided to the DebugService's SetVariable method. @@ -16,7 +16,7 @@ public class InvalidPowerShellExpressionException : Exception /// Initializes a new instance of the SetVariableExpressionException class. /// /// Message indicating why the expression is invalid. - public InvalidPowerShellExpressionException(string message) + public InvalidPowerShellExpressionException(string message) : base(message) { } diff --git a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs similarity index 96% rename from src/PowerShellEditorServices/Debugging/StackFrameDetails.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs index e217457fc..f99476980 100644 --- a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs @@ -3,10 +3,9 @@ // 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 +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Contains details pertaining to a single stack frame in @@ -57,7 +56,7 @@ public class StackFrameDetails public int? EndColumnNumber { get; internal set; } /// - /// Gets a boolean value indicating whether or not the stack frame is executing + /// 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; } @@ -107,9 +106,9 @@ static internal StackFrameDetails Create( 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 + // TODO: RKH 2019-03-07 Temporarily disable "external" code until I have a chance to add // settings to control this feature. - //if (workspaceRootPath != null && + //if (workspaceRootPath != null && // invocationInfo != null && // !scriptPath.StartsWith(workspaceRootPath, StringComparison.OrdinalIgnoreCase)) //{ diff --git a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableContainerDetails.cs similarity index 97% rename from src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableContainerDetails.cs index 2c90d8891..75d264039 100644 --- a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableContainerDetails.cs @@ -5,9 +5,10 @@ using System.Collections.Generic; using System.Diagnostics; +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Container for variables that is not itself a variable per se. However given how diff --git a/src/PowerShellEditorServices/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Debugging/VariableDetails.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 25ee71057..5e181e954 100644 --- a/src/PowerShellEditorServices/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -10,9 +10,10 @@ using System.Linq; using System.Management.Automation; using System.Reflection; +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Contains details pertaining to a variable in the current @@ -349,7 +350,7 @@ private VariableDetails[] GetChildren(object obj, ILogger logger) // 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}"); + logger.LogWarning($"Failed to get properties of variable {this.Name}, value invocation was attempted: {ex.Message}"); } return childVariables.ToArray(); @@ -381,7 +382,7 @@ private static void AddDotNetProperties(object obj, List childV { // Some properties can throw exceptions, add the property // name and info about the error. - if (ex.GetType() == typeof (TargetInvocationException)) + if (ex is TargetInvocationException) { ex = ex.InnerException; } diff --git a/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs similarity index 95% rename from src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs index aada558fa..357ebbea3 100644 --- a/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs @@ -3,9 +3,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Defines the common details between a variable and a variable container such as a scope diff --git a/src/PowerShellEditorServices/Debugging/VariableScope.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableScope.cs similarity index 94% rename from src/PowerShellEditorServices/Debugging/VariableScope.cs rename to src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableScope.cs index 53c98058b..2e4a013e9 100644 --- a/src/PowerShellEditorServices/Debugging/VariableScope.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableScope.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { /// /// Contains details pertaining to a variable scope in the current diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs new file mode 100644 index 000000000..8b65c4eea --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs @@ -0,0 +1,226 @@ +// +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.DebugAdapter.Protocol.Models; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class SetFunctionBreakpointsHandler : ISetFunctionBreakpointsHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + private readonly DebugStateService _debugStateService; + + public SetFunctionBreakpointsHandler( + ILoggerFactory loggerFactory, + DebugService debugService, + DebugStateService debugStateService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + _debugStateService = debugStateService; + } + + public async Task Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken) + { + CommandBreakpointDetails[] breakpointDetails = request.Breakpoints + .Select((funcBreakpoint) => CommandBreakpointDetails.Create( + funcBreakpoint.Name, + funcBreakpoint.Condition, + funcBreakpoint.HitCondition)) + .ToArray(); + + // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. + CommandBreakpointDetails[] updatedBreakpointDetails = breakpointDetails; + if (!_debugStateService.NoDebug) + { + _debugStateService.SetBreakpointInProgress = true; + + try + { + updatedBreakpointDetails = + await _debugService.SetCommandBreakpointsAsync( + breakpointDetails); + } + catch (Exception e) + { + // Log whatever the error is + _logger.LogException($"Caught error while setting command breakpoints", e); + } + finally + { + _debugStateService.SetBreakpointInProgress = false; + } + } + + return new SetFunctionBreakpointsResponse + { + Breakpoints = updatedBreakpointDetails + .Select(LspDebugUtils.CreateBreakpoint) + .ToArray() + }; + } + } + + internal class SetExceptionBreakpointsHandler : ISetExceptionBreakpointsHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + private readonly DebugStateService _debugStateService; + + public SetExceptionBreakpointsHandler( + ILoggerFactory loggerFactory, + DebugService debugService, + DebugStateService debugStateService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + _debugStateService = debugStateService; + } + + public Task Handle(SetExceptionBreakpointsArguments request, CancellationToken cancellationToken) + { + // 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; + // } + //} + + return Task.FromResult(new SetExceptionBreakpointsResponse()); + } + } + + internal class SetBreakpointsHandler : ISetBreakpointsHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + private readonly DebugStateService _debugStateService; + private readonly WorkspaceService _workspaceService; + + public SetBreakpointsHandler( + ILoggerFactory loggerFactory, + DebugService debugService, + DebugStateService debugStateService, + WorkspaceService workspaceService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + _debugStateService = debugStateService; + _workspaceService = workspaceService; + } + + public async Task Handle(SetBreakpointsArguments request, CancellationToken cancellationToken) + { + 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(request.Source.Path) && + !_workspaceService.TryGetFile( + request.Source.Path, + out scriptFile)) + { + string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set."; + var srcBreakpoints = request.Breakpoints + .Select(srcBkpt => LspDebugUtils.CreateBreakpoint( + srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug)); + + // Return non-verified breakpoint message. + return new SetBreakpointsResponse + { + Breakpoints = new Container(srcBreakpoints) + }; + } + + // Verify source file is a PowerShell script file. + string fileExtension = Path.GetExtension(scriptFile?.FilePath ?? "")?.ToLower(); + if (string.IsNullOrEmpty(fileExtension) || ((fileExtension != ".ps1") && (fileExtension != ".psm1"))) + { + _logger.LogWarning( + $"Attempted to set breakpoints on a non-PowerShell file: {request.Source.Path}"); + + string message = _debugStateService.NoDebug ? string.Empty : "Source is not a PowerShell script, breakpoint not set."; + + var srcBreakpoints = request.Breakpoints + .Select(srcBkpt => LspDebugUtils.CreateBreakpoint( + srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug)); + + // Return non-verified breakpoint message. + return new SetBreakpointsResponse + { + Breakpoints = new Container(srcBreakpoints) + }; + } + + // At this point, the source file has been verified as a PowerShell script. + BreakpointDetails[] breakpointDetails = request.Breakpoints + .Select((srcBreakpoint) => BreakpointDetails.Create( + scriptFile.FilePath, + (int)srcBreakpoint.Line, + (int?)srcBreakpoint.Column, + srcBreakpoint.Condition, + srcBreakpoint.HitCondition)) + .ToArray(); + + // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. + BreakpointDetails[] updatedBreakpointDetails = breakpointDetails; + if (!_debugStateService.NoDebug) + { + _debugStateService.SetBreakpointInProgress = true; + + try + { + updatedBreakpointDetails = + await _debugService.SetLineBreakpointsAsync( + scriptFile, + breakpointDetails); + } + catch (Exception e) + { + // Log whatever the error is + _logger.LogException($"Caught error while setting breakpoints in SetBreakpoints handler for file {scriptFile?.FilePath}", e); + } + finally + { + _debugStateService.SetBreakpointInProgress = false; + } + } + + return new SetBreakpointsResponse + { + Breakpoints = new Container(updatedBreakpointDetails + .Select(LspDebugUtils.CreateBreakpoint)) + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs new file mode 100644 index 000000000..addf829a1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -0,0 +1,113 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.DebugAdapter.Protocol.Events; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class ConfigurationDoneHandler : IConfigurationDoneHandler + { + private readonly ILogger _logger; + private readonly IJsonRpcServer _jsonRpcServer; + private readonly DebugService _debugService; + private readonly DebugStateService _debugStateService; + private readonly DebugEventHandlerService _debugEventHandlerService; + private readonly PowerShellContextService _powerShellContextService; + private readonly WorkspaceService _workspaceService; + + public ConfigurationDoneHandler( + ILoggerFactory loggerFactory, + IJsonRpcServer jsonRpcServer, + DebugService debugService, + DebugStateService debugStateService, + DebugEventHandlerService debugEventHandlerService, + PowerShellContextService powerShellContextService, + WorkspaceService workspaceService) + { + _logger = loggerFactory.CreateLogger(); + _jsonRpcServer = jsonRpcServer; + _debugService = debugService; + _debugStateService = debugStateService; + _debugEventHandlerService = debugEventHandlerService; + _powerShellContextService = powerShellContextService; + _workspaceService = workspaceService; + } + + public Task Handle(ConfigurationDoneArguments request, CancellationToken cancellationToken) + { + _debugService.IsClientAttached = true; + + if (!string.IsNullOrEmpty(_debugStateService.ScriptToLaunch)) + { + if (_powerShellContextService.SessionState == PowerShellContextState.Ready) + { + // Configuration is done, launch the script + var nonAwaitedTask = LaunchScriptAsync(_debugStateService.ScriptToLaunch) + .ConfigureAwait(continueOnCapturedContext: false); + } + else + { + _logger.LogTrace("configurationDone request called after script was already launched, skipping it."); + } + } + + if (_debugStateService.IsInteractiveDebugSession) + { + if (_debugStateService.OwnsEditorSession) + { + // If this is a debug-only session, we need to start + // the command loop manually + // TODO: Bring this back + //_editorSession.HostInput.StartCommandLoop(); + } + + if (_debugService.IsDebuggerStopped) + { + if (_debugService.CurrentDebuggerStoppedEventArgs != null) + { + // If this is an interactive session and there's a pending breakpoint, + // send that information along to the debugger client + _debugEventHandlerService.TriggerDebuggerStopped(_debugService.CurrentDebuggerStoppedEventArgs); + } + else + { + // If this is an interactive session and there's a pending breakpoint that has not been propagated through + // the debug service, fire the debug service's OnDebuggerStop event. + _debugService.OnDebuggerStopAsync(null, _powerShellContextService.CurrentDebuggerStopEventArgs); + } + } + } + + return Task.FromResult(new ConfigurationDoneResponse()); + } + + private async Task LaunchScriptAsync(string scriptToLaunch) + { + // Is this an untitled script? + if (ScriptFile.IsUntitledPath(scriptToLaunch)) + { + ScriptFile untitledScript = _workspaceService.GetFile(scriptToLaunch); + + await _powerShellContextService + .ExecuteScriptStringAsync(untitledScript.Contents, true, true); + } + else + { + await _powerShellContextService + .ExecuteScriptWithArgsAsync(scriptToLaunch, _debugStateService.Arguments, writeInputToHost: true); + } + + _jsonRpcServer.SendNotification(EventNames.Terminated); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs new file mode 100644 index 000000000..246621ce7 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -0,0 +1,88 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class DebugEvaluateHandler : IEvaluateHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + private readonly DebugService _debugService; + + public DebugEvaluateHandler( + ILoggerFactory factory, + PowerShellContextService powerShellContextService, + DebugService debugService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + _debugService = debugService; + } + + public async Task Handle(EvaluateRequestArguments request, CancellationToken cancellationToken) + { + string valueString = ""; + int variableId = 0; + + bool isFromRepl = + string.Equals( + request.Context, + "repl", + StringComparison.CurrentCultureIgnoreCase); + + if (isFromRepl) + { + var notAwaited = + _powerShellContextService + .ExecuteScriptStringAsync(request.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 (_powerShellContextService.IsDebuggerStopped) + { + // First check to see if the watch expression refers to a naked variable reference. + result = + _debugService.GetVariableFromExpression(request.Expression, request.FrameId); + + // If the expression is not a naked variable reference, then evaluate the expression. + if (result == null) + { + result = + await _debugService.EvaluateExpressionAsync( + request.Expression, + request.FrameId, + isFromRepl); + } + } + + if (result != null) + { + valueString = result.ValueString; + variableId = + result.IsExpandable ? + result.Id : 0; + } + } + + return new EvaluateResponseBody + { + Result = valueString, + VariablesReference = variableId + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebuggerActionHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebuggerActionHandlers.cs new file mode 100644 index 000000000..21c520881 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebuggerActionHandlers.cs @@ -0,0 +1,123 @@ +// +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class ContinueHandler : IContinueHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public ContinueHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(ContinueArguments request, CancellationToken cancellationToken) + { + _debugService.Continue(); + return Task.FromResult(new ContinueResponse()); + } + } + + internal class NextHandler : INextHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public NextHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(NextArguments request, CancellationToken cancellationToken) + { + _debugService.StepOver(); + return Task.FromResult(new NextResponse()); + } + } + + internal class PauseHandler : IPauseHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public PauseHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(PauseArguments request, CancellationToken cancellationToken) + { + try + { + _debugService.Break(); + return Task.FromResult(new PauseResponse()); + } + catch(NotSupportedException e) + { + throw new RpcErrorException(0, e.Message); + } + } + } + + internal class StepInHandler : IStepInHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public StepInHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(StepInArguments request, CancellationToken cancellationToken) + { + _debugService.StepIn(); + return Task.FromResult(new StepInResponse()); + } + } + + internal class StepOutHandler : IStepOutHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public StepOutHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(StepOutArguments request, CancellationToken cancellationToken) + { + _debugService.StepOut(); + return Task.FromResult(new StepOutResponse()); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs new file mode 100644 index 000000000..61df26447 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -0,0 +1,84 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Server; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class DisconnectHandler : IDisconnectHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + private readonly DebugService _debugService; + private readonly DebugStateService _debugStateService; + private readonly DebugEventHandlerService _debugEventHandlerService; + private readonly PsesDebugServer _psesDebugServer; + + public DisconnectHandler( + ILoggerFactory factory, + PsesDebugServer psesDebugServer, + PowerShellContextService powerShellContextService, + DebugService debugService, + DebugStateService debugStateService, + DebugEventHandlerService debugEventHandlerService) + { + _logger = factory.CreateLogger(); + _psesDebugServer = psesDebugServer; + _powerShellContextService = powerShellContextService; + _debugService = debugService; + _debugStateService = debugStateService; + _debugEventHandlerService = debugEventHandlerService; + } + + public async Task Handle(DisconnectArguments request, CancellationToken cancellationToken) + { + _debugEventHandlerService.UnregisterEventHandlers(); + if (_debugStateService.ExecutionCompleted == false) + { + _debugStateService.ExecutionCompleted = true; + _powerShellContextService.AbortExecution(shouldAbortDebugSession: true); + + if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession) + { + // Pop the sessions + if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess) + { + try + { + await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSHostProcess"); + + if (_debugStateService.IsRemoteAttach && + _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + { + await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSSession"); + } + } + catch (Exception e) + { + _logger.LogException("Caught exception while popping attached process after debugging", e); + } + } + } + + _debugService.IsClientAttached = false; + } + + _logger.LogInformation("Debug adapter is shutting down..."); + + // Trigger the clean up of the debugger. + Task.Run(_psesDebugServer.OnSessionEnded); + + return new DisconnectResponse(); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/InitializeHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/InitializeHandler.cs new file mode 100644 index 000000000..77861d443 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/InitializeHandler.cs @@ -0,0 +1,43 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class InitializeHandler : IInitializeHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public InitializeHandler( + ILoggerFactory factory, + DebugService debugService) + { + _logger = factory.CreateLogger(); + _debugService = debugService; + } + + public async Task Handle(InitializeRequestArguments request, CancellationToken cancellationToken) + { + // Clear any existing breakpoints before proceeding + await _debugService.ClearAllBreakpointsAsync(); + + // Now send the Initialize response to continue setup + return new InitializeResponse + { + SupportsConfigurationDoneRequest = true, + SupportsFunctionBreakpoints = true, + SupportsConditionalBreakpoints = true, + SupportsHitConditionalBreakpoints = true, + SupportsSetVariable = true + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs new file mode 100644 index 000000000..1772d4a13 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -0,0 +1,407 @@ +// +// 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.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using OmniSharp.Extensions.DebugAdapter.Protocol.Events; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("launch")] + interface IPsesLaunchHandler : IJsonRpcRequestHandler { } + + [Serial, Method("attach")] + interface IPsesAttachHandler : IJsonRpcRequestHandler { } + + public class PsesLaunchRequestArguments : IRequest + { + /// + /// 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; } + } + + public class PsesAttachRequestArguments : IRequest + { + 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; } + } + + internal class LaunchHandler : IPsesLaunchHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + private readonly PowerShellContextService _powerShellContextService; + private readonly DebugStateService _debugStateService; + private readonly DebugEventHandlerService _debugEventHandlerService; + private readonly IJsonRpcServer _jsonRpcServer; + private readonly RemoteFileManagerService _remoteFileManagerService; + + public LaunchHandler( + ILoggerFactory factory, + IJsonRpcServer jsonRpcServer, + DebugService debugService, + PowerShellContextService powerShellContextService, + DebugStateService debugStateService, + DebugEventHandlerService debugEventHandlerService, + RemoteFileManagerService remoteFileManagerService) + { + _logger = factory.CreateLogger(); + _jsonRpcServer = jsonRpcServer; + _debugService = debugService; + _powerShellContextService = powerShellContextService; + _debugStateService = debugStateService; + _debugEventHandlerService = debugEventHandlerService; + _remoteFileManagerService = remoteFileManagerService; + } + + public async Task Handle(PsesLaunchRequestArguments request, CancellationToken cancellationToken) + { + _debugEventHandlerService.RegisterEventHandlers(); + + // Determine whether or not the working directory should be set in the PowerShellContext. + if ((_powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Local) && + !_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 = request.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.LogError( + $"The specified 'cwd' path is invalid: '{request.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) && request.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 _powerShellContextService.SetWorkingDirectoryAsync(workingDir, isPathAlreadyEscaped: false); + } + + _logger.LogTrace($"Working dir " + (string.IsNullOrEmpty(workingDir) ? "not set." : $"set to '{workingDir}'")); + } + + // Prepare arguments to the script - if specified + string arguments = null; + if ((request.Args != null) && (request.Args.Length > 0)) + { + arguments = string.Join(" ", request.Args); + _logger.LogTrace("Script arguments are: " + arguments); + } + + // Store the launch parameters so that they can be used later + _debugStateService.NoDebug = request.NoDebug; + _debugStateService.ScriptToLaunch = request.Script; + _debugStateService.Arguments = arguments; + _debugStateService.IsUsingTempIntegratedConsole = request.CreateTemporaryIntegratedConsole; + + // TODO: Bring this back + // If the current session is remote, map the script path to the remote + // machine if necessary + if (_debugStateService.ScriptToLaunch != null && + _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + { + _debugStateService.ScriptToLaunch = + _remoteFileManagerService.GetMappedPath( + _debugStateService.ScriptToLaunch, + _powerShellContextService.CurrentRunspace); + } + + // If no script is being launched, mark this as an interactive + // debugging session + _debugStateService.IsInteractiveDebugSession = string.IsNullOrEmpty(_debugStateService.ScriptToLaunch); + + // Send the InitializedEvent so that the debugger will continue + // sending configuration requests + _jsonRpcServer.SendNotification(EventNames.Initialized); + + return Unit.Value; + } + } + + internal class AttachHandler : IPsesAttachHandler + { + private static readonly Version s_minVersionForCustomPipeName = new Version(6, 2); + + private readonly ILogger _logger; + private readonly DebugService _debugService; + private readonly PowerShellContextService _powerShellContextService; + private readonly DebugStateService _debugStateService; + private readonly DebugEventHandlerService _debugEventHandlerService; + private readonly IJsonRpcServer _jsonRpcServer; + + public AttachHandler( + ILoggerFactory factory, + IJsonRpcServer jsonRpcServer, + DebugService debugService, + PowerShellContextService powerShellContextService, + DebugStateService debugStateService, + DebugEventHandlerService debugEventHandlerService) + { + _logger = factory.CreateLogger(); + _jsonRpcServer = jsonRpcServer; + _debugService = debugService; + _powerShellContextService = powerShellContextService; + _debugStateService = debugStateService; + _debugEventHandlerService = debugEventHandlerService; + } + + public async Task Handle(PsesAttachRequestArguments request, CancellationToken cancellationToken) + { + _debugStateService.IsAttachSession = true; + + _debugEventHandlerService.RegisterEventHandlers(); + + bool processIdIsSet = !string.IsNullOrEmpty(request.ProcessId) && request.ProcessId != "undefined"; + bool customPipeNameIsSet = !string.IsNullOrEmpty(request.CustomPipeName) && request.CustomPipeName != "undefined"; + + PowerShellVersionDetails runspaceVersion = + _powerShellContextService.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.LogInformation( + $"Attach request aborted, received {request.ProcessId} for processId."); + + throw new RpcErrorException(0, "User aborted attach to PowerShell host process."); + } + + StringBuilder errorMessages = new StringBuilder(); + + if (request.ComputerName != null) + { + if (runspaceVersion.Version.Major < 4) + { + throw new RpcErrorException(0, $"Remote sessions are only available with PowerShell 4 and higher (current session is {runspaceVersion.Version})."); + } + else if (_powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + { + throw new RpcErrorException(0, $"Cannot attach to a process in a remote session when already in a remote session."); + } + + await _powerShellContextService.ExecuteScriptStringAsync( + $"Enter-PSSession -ComputerName \"{request.ComputerName}\"", + errorMessages); + + if (errorMessages.Length > 0) + { + throw new RpcErrorException(0, $"Could not establish remote session to computer '{request.ComputerName}'"); + } + + _debugStateService.IsRemoteAttach = true; + } + + if (processIdIsSet && int.TryParse(request.ProcessId, out int processId) && (processId > 0)) + { + if (runspaceVersion.Version.Major < 5) + { + throw new RpcErrorException(0, $"Attaching to a process is only available with PowerShell 5 and higher (current session is {runspaceVersion.Version})."); + } + + await _powerShellContextService.ExecuteScriptStringAsync( + $"Enter-PSHostProcess -Id {processId}", + errorMessages); + + if (errorMessages.Length > 0) + { + throw new RpcErrorException(0, $"Could not attach to process '{processId}'"); + } + } + else if (customPipeNameIsSet) + { + if (runspaceVersion.Version < s_minVersionForCustomPipeName) + { + throw new RpcErrorException(0, $"Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is {runspaceVersion.Version})."); + } + + await _powerShellContextService.ExecuteScriptStringAsync( + $"Enter-PSHostProcess -CustomPipeName {request.CustomPipeName}", + errorMessages); + + if (errorMessages.Length > 0) + { + throw new RpcErrorException(0, $"Could not attach to process with CustomPipeName: '{request.CustomPipeName}'"); + } + } + else if (request.ProcessId != "current") + { + _logger.LogError( + $"Attach request failed, '{request.ProcessId}' is an invalid value for the processId."); + + throw new RpcErrorException(0, "A positive integer must be specified for the processId field."); + } + + // Clear any existing breakpoints before proceeding + await _debugService.ClearAllBreakpointsAsync().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 (request.RunspaceName != null) + { + debugRunspaceCmd = $"\nDebug-Runspace -Name '{request.RunspaceName}'"; + } + else if (request.RunspaceId != null) + { + if (!int.TryParse(request.RunspaceId, out int runspaceId) || runspaceId <= 0) + { + _logger.LogError( + $"Attach request failed, '{request.RunspaceId}' is an invalid value for the processId."); + + throw new RpcErrorException(0, "A positive integer must be specified for the RunspaceId field."); + } + + debugRunspaceCmd = $"\nDebug-Runspace -Id {runspaceId}"; + } + else + { + debugRunspaceCmd = "\nDebug-Runspace -Id 1"; + } + + _debugStateService.WaitingForAttach = true; + Task nonAwaitedTask = _powerShellContextService + .ExecuteScriptStringAsync(debugRunspaceCmd) + .ContinueWith(OnExecutionCompletedAsync); + + return Unit.Value; + } + + private async Task OnExecutionCompletedAsync(Task executeTask) + { + try + { + await executeTask; + } + catch (Exception e) + { + _logger.LogError( + "Exception occurred while awaiting debug launch task.\n\n" + e.ToString()); + } + + _logger.LogTrace("Execution completed, terminating..."); + + //_debugStateService.ExecutionCompleted = true; + + //_debugEventHandlerService.UnregisterEventHandlers(); + + //if (_debugStateService.IsAttachSession) + //{ + // // Pop the sessions + // if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess) + // { + // try + // { + // await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSHostProcess"); + + // if (_debugStateService.IsRemoteAttach && + // _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + // { + // await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSSession"); + // } + // } + // catch (Exception e) + // { + // _logger.LogException("Caught exception while popping attached process after debugging", e); + // } + // } + //} + + //_debugService.IsClientAttached = false; + _jsonRpcServer.SendNotification(EventNames.Terminated); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ScopesHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ScopesHandler.cs new file mode 100644 index 000000000..02c989611 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ScopesHandler.cs @@ -0,0 +1,44 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.DebugAdapter.Protocol.Models; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class ScopesHandler : IScopesHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public ScopesHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(ScopesArguments request, CancellationToken cancellationToken) + { + VariableScope[] variableScopes = + _debugService.GetVariableScopes( + (int) request.FrameId); + + return Task.FromResult(new ScopesResponse + { + Scopes = new Container(variableScopes + .Select(LspDebugUtils.CreateScope)) + }); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs new file mode 100644 index 000000000..dbd4e0939 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs @@ -0,0 +1,67 @@ +// +// 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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.DebugAdapter.Protocol.Models; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class SetVariableHandler : ISetVariableHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public SetVariableHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public async Task Handle(SetVariableArguments request, CancellationToken cancellationToken) + { + try + { + string updatedValue = + await _debugService.SetVariableAsync( + (int) request.VariablesReference, + request.Name, + request.Value); + + return new SetVariableResponse + { + Value = updatedValue + }; + + } + 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.LogTrace($"Failed to set variable: {ex.Message}"); + throw new RpcErrorException(0, ex.Message); + } + catch (Exception ex) + { + _logger.LogError($"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."; + throw new RpcErrorException(0, msg); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SourceHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SourceHandler.cs new file mode 100644 index 000000000..0b41345b3 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SourceHandler.cs @@ -0,0 +1,21 @@ +// +// 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 OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class SourceHandler : ISourceHandler + { + public Task Handle(SourceArguments request, CancellationToken cancellationToken) + { + // TODO: Implement this message. For now, doesn't seem to + // be a problem that it's missing. + return Task.FromResult(new SourceResponse()); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs new file mode 100644 index 000000000..57061c317 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs @@ -0,0 +1,80 @@ +// +// 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.DebugAdapter.Protocol.Models; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class StackTraceHandler : IStackTraceHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public StackTraceHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(StackTraceArguments request, CancellationToken cancellationToken) + { + StackFrameDetails[] stackFrameDetails = + _debugService.GetStackFrames(); + + // Handle a rare race condition where the adapter requests stack frames before they've + // begun building. + if (stackFrameDetails == null) + { + return Task.FromResult(new StackTraceResponse + { + StackFrames = new StackFrame[0], + TotalFrames = 0 + }); + } + + List newStackFrames = new List(); + + long startFrameIndex = request.StartFrame ?? 0; + long maxFrameCount = stackFrameDetails.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. + long requestedFrameCount = (request.Levels ?? 0); + if (requestedFrameCount > 0) + { + maxFrameCount = Math.Min(maxFrameCount, startFrameIndex + requestedFrameCount); + } + + for (long 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( + // stackFrameDetails[i], + // i)); + newStackFrames.Add( + LspDebugUtils.CreateStackFrame(stackFrameDetails[i], id: i)); + } + + return Task.FromResult(new StackTraceResponse + { + StackFrames = newStackFrames, + TotalFrames = newStackFrames.Count + }); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ThreadsHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ThreadsHandler.cs new file mode 100644 index 000000000..800494a2a --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ThreadsHandler.cs @@ -0,0 +1,29 @@ +// +// 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 OmniSharp.Extensions.DebugAdapter.Protocol.Models; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class ThreadsHandler : IThreadsHandler + { + public Task Handle(ThreadsArguments request, CancellationToken cancellationToken) + { + return Task.FromResult(new ThreadsResponse + { + // TODO: What do I do with these? + Threads = new Container( + new OmniSharp.Extensions.DebugAdapter.Protocol.Models.Thread + { + Id = 1, + Name = "Main Thread" + }) + }); + } + } +} diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs new file mode 100644 index 000000000..5854d7a58 --- /dev/null +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs @@ -0,0 +1,58 @@ +// +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.DebugAdapter.Protocol.Models; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class VariablesHandler : IVariablesHandler + { + private readonly ILogger _logger; + private readonly DebugService _debugService; + + public VariablesHandler( + ILoggerFactory loggerFactory, + DebugService debugService) + { + _logger = loggerFactory.CreateLogger(); + _debugService = debugService; + } + + public Task Handle(VariablesArguments request, CancellationToken cancellationToken) + { + VariableDetailsBase[] variables = + _debugService.GetVariables( + (int)request.VariablesReference); + + VariablesResponse variablesResponse = null; + + try + { + variablesResponse = new VariablesResponse + { + Variables = + variables + .Select(LspDebugUtils.CreateVariable) + .ToArray() + }; + } + catch (Exception) + { + // TODO: This shouldn't be so broad + } + + return Task.FromResult(variablesResponse); + } + } +} diff --git a/src/PowerShellEditorServices/Console/ChoiceDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoiceDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Console/ChoiceDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoiceDetails.cs index d8121b6c1..d93d0daca 100644 --- a/src/PowerShellEditorServices/Console/ChoiceDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoiceDetails.cs @@ -6,11 +6,11 @@ using System; using System.Management.Automation.Host; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Contains the details about a choice that should be displayed - /// to the user. This class is meant to be serializable to the + /// to the user. This class is meant to be serializable to the /// user's UI. /// public class ChoiceDetails diff --git a/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs similarity index 99% rename from src/PowerShellEditorServices/Console/ChoicePromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs index 579d5b708..ecfe1ffde 100644 --- a/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs @@ -9,9 +9,9 @@ using System.Management.Automation; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Indicates the style of prompt to be displayed. @@ -351,4 +351,3 @@ private int GetSingleResult(int[] choiceArray) #endregion } } - diff --git a/src/PowerShellEditorServices/Console/CollectionFieldDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Console/CollectionFieldDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs index 80ae62b5f..bfcf4cec8 100644 --- a/src/PowerShellEditorServices/Console/CollectionFieldDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs @@ -6,7 +6,7 @@ using System; using System.Collections; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Contains the details of an colleciton input field shown diff --git a/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs similarity index 96% rename from src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs index 74b51c876..bf209aeb0 100644 --- a/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs @@ -4,11 +4,9 @@ // using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a standard implementation of ChoicePromptHandler diff --git a/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs similarity index 94% rename from src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs index 8124633a7..1c9187b4f 100644 --- a/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs @@ -4,12 +4,9 @@ // using System; -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a standard implementation of InputPromptHandler diff --git a/src/PowerShellEditorServices/Console/ConsoleProxy.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs similarity index 98% rename from src/PowerShellEditorServices/Console/ConsoleProxy.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs index c38c81724..cda8997fe 100644 --- a/src/PowerShellEditorServices/Console/ConsoleProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides asynchronous implementations of the API's as well as @@ -189,7 +189,7 @@ internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken can } catch (OperationCanceledException) { - return default; + return default(ConsoleKeyInfo); } } } diff --git a/src/PowerShellEditorServices/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs similarity index 99% rename from src/PowerShellEditorServices/Console/ConsoleReadLine.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs index af6ca044c..0c306944e 100644 --- a/src/PowerShellEditorServices/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { using System; using System.Management.Automation; @@ -19,13 +19,13 @@ namespace Microsoft.PowerShell.EditorServices.Console internal class ConsoleReadLine { #region Private Field - private PowerShellContext powerShellContext; + private PowerShellContextService powerShellContext; #endregion #region Constructors - public ConsoleReadLine(PowerShellContext powerShellContext) + public ConsoleReadLine(PowerShellContextService powerShellContext) { this.powerShellContext = powerShellContext; } diff --git a/src/PowerShellEditorServices/Console/CredentialFieldDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Console/CredentialFieldDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs index 4b4452f2b..5c27a70f6 100644 --- a/src/PowerShellEditorServices/Console/CredentialFieldDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs @@ -7,7 +7,7 @@ using System.Management.Automation; using System.Security; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Contains the details of a PSCredential field shown diff --git a/src/PowerShellEditorServices/Console/FieldDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs similarity index 97% rename from src/PowerShellEditorServices/Console/FieldDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs index ffec536b3..3fe573659 100644 --- a/src/PowerShellEditorServices/Console/FieldDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections; @@ -10,7 +11,7 @@ using System.Management.Automation.Host; using System.Reflection; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Contains the details of an input field shown from an @@ -133,8 +134,7 @@ public object GetValue(ILogger logger) else { // This "shoudln't" happen, so log in case it does - logger.Write( - LogLevel.Error, + logger.LogError( $"Cannot retrieve value for field {this.Label}"); } } @@ -218,8 +218,7 @@ private static Type GetFieldTypeFromTypeName( { if (!LanguagePrimitives.TryConvertTo(assemblyFullName, out fieldType)) { - logger.Write( - LogLevel.Warning, + logger.LogWarning( string.Format( "Could not resolve type of field: {0}", assemblyFullName)); diff --git a/src/PowerShellEditorServices/Console/IConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs similarity index 98% rename from src/PowerShellEditorServices/Console/IConsoleOperations.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs index b3fb58561..2bfeb883c 100644 --- a/src/PowerShellEditorServices/Console/IConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides platform specific console utilities. diff --git a/src/PowerShellEditorServices/Console/InputPromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs similarity index 99% rename from src/PowerShellEditorServices/Console/InputPromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs index 7d626c9ab..ed95b9120 100644 --- a/src/PowerShellEditorServices/Console/InputPromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs @@ -10,9 +10,9 @@ using System.Security; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a base implementation for IPromptHandler classes diff --git a/src/PowerShellEditorServices/Console/PromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs similarity index 93% rename from src/PowerShellEditorServices/Console/PromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs index a40bd6e76..97e8c4c6a 100644 --- a/src/PowerShellEditorServices/Console/PromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs @@ -4,9 +4,9 @@ // using System; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Defines an abstract base class for prompt handler implementations. diff --git a/src/PowerShellEditorServices/Console/TerminalChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs similarity index 94% rename from src/PowerShellEditorServices/Console/TerminalChoicePromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs index 1bf5a5cc4..f49c6ca39 100644 --- a/src/PowerShellEditorServices/Console/TerminalChoicePromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs @@ -3,12 +3,11 @@ // 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; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a standard implementation of ChoicePromptHandler diff --git a/src/PowerShellEditorServices/Console/TerminalInputPromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs similarity index 95% rename from src/PowerShellEditorServices/Console/TerminalInputPromptHandler.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs index 67b58bc55..dacb51f32 100644 --- a/src/PowerShellEditorServices/Console/TerminalInputPromptHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs @@ -3,13 +3,12 @@ // 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; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a standard implementation of InputPromptHandler diff --git a/src/PowerShellEditorServices/Console/UnixConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs similarity index 97% rename from src/PowerShellEditorServices/Console/UnixConsoleOperations.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs index e0ef73b65..7df7b186e 100644 --- a/src/PowerShellEditorServices/Console/UnixConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs @@ -9,7 +9,7 @@ using Microsoft.PowerShell.EditorServices.Utility; using UnixConsoleEcho; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class UnixConsoleOperations : IConsoleOperations { @@ -49,7 +49,7 @@ public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToke // 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. - if (Utils.IsPS6) + if (VersionUtils.IsPS6) { InputEcho.Disable(); } @@ -63,7 +63,7 @@ public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToke } finally { - if (Utils.IsPS6) + if (VersionUtils.IsPS6) { InputEcho.Disable(); } @@ -89,17 +89,18 @@ public async Task ReadKeyAsync(bool intercept, 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`. - if (Utils.IsPS6) + if (VersionUtils.IsPS6) { InputEcho.Disable(); } + try { while (!await WaitForKeyAvailableAsync(cancellationToken)); } finally { - if (Utils.IsPS6) + if (VersionUtils.IsPS6) { InputEcho.Enable(); } diff --git a/src/PowerShellEditorServices/Console/WindowsConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs similarity index 97% rename from src/PowerShellEditorServices/Console/WindowsConsoleOperations.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs index 493e66930..69c6ac91e 100644 --- a/src/PowerShellEditorServices/Console/WindowsConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class WindowsConsoleOperations : IConsoleOperations { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs new file mode 100644 index 000000000..1d6ddd247 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs @@ -0,0 +1,183 @@ +// +// 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.Handlers; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services +{ + internal class EditorOperationsService : IEditorOperations + { + private const bool DefaultPreviewSetting = true; + + private WorkspaceService _workspaceService; + private ILanguageServer _languageServer; + + public EditorOperationsService( + WorkspaceService workspaceService, + ILanguageServer languageServer) + { + this._workspaceService = workspaceService; + this._languageServer = languageServer; + } + + public async Task GetEditorContextAsync() + { + ClientEditorContext clientContext = + await _languageServer.SendRequest( + "editor/getEditorContext", + new GetEditorContextRequest()); + + return this.ConvertClientEditorContext(clientContext); + } + + public async Task InsertTextAsync(string filePath, string text, BufferRange insertRange) + { + await _languageServer.SendRequest("editor/insertText", new InsertTextRequest + { + FilePath = filePath, + InsertText = text, + InsertRange = + new Range + { + Start = new Position + { + Line = insertRange.Start.Line - 1, + Character = insertRange.Start.Column - 1 + }, + End = new Position + { + Line = insertRange.End.Line - 1, + Character = insertRange.End.Column - 1 + } + } + }); + } + + public async Task SetSelectionAsync(BufferRange selectionRange) + { + + await _languageServer.SendRequest("editor/setSelection", new SetSelectionRequest + { + SelectionRange = + new Range + { + Start = new Position + { + Line = selectionRange.Start.Line - 1, + Character = selectionRange.Start.Column - 1 + }, + End = new Position + { + Line = selectionRange.End.Line - 1, + Character = selectionRange.End.Column - 1 + } + } + }); + } + + public EditorContext ConvertClientEditorContext( + ClientEditorContext clientContext) + { + ScriptFile scriptFile = _workspaceService.CreateScriptFileFromFileBuffer( + clientContext.CurrentFilePath, + clientContext.CurrentFileContent); + + return + new EditorContext( + this, + scriptFile, + new BufferPosition( + (int) clientContext.CursorPosition.Line + 1, + (int) clientContext.CursorPosition.Character + 1), + new BufferRange( + (int) clientContext.SelectionRange.Start.Line + 1, + (int) clientContext.SelectionRange.Start.Character + 1, + (int) clientContext.SelectionRange.End.Line + 1, + (int) clientContext.SelectionRange.End.Character + 1), + clientContext.CurrentFileLanguage); + } + + public async Task NewFileAsync() + { + await _languageServer.SendRequest("editor/newFile", null); + } + + public async Task OpenFileAsync(string filePath) + { + await _languageServer.SendRequest("editor/openFile", new OpenFileDetails + { + FilePath = filePath, + Preview = DefaultPreviewSetting + }); + } + + public async Task OpenFileAsync(string filePath, bool preview) + { + await _languageServer.SendRequest("editor/openFile", new OpenFileDetails + { + FilePath = filePath, + Preview = preview + }); + } + + public async Task CloseFileAsync(string filePath) + { + await _languageServer.SendRequest("editor/closeFile", filePath); + } + + public async Task SaveFileAsync(string filePath) + { + await SaveFileAsync(filePath, null); + } + + public async Task SaveFileAsync(string currentPath, string newSavePath) + { + await _languageServer.SendRequest("editor/saveFile", new SaveFileDetails + { + FilePath = currentPath, + NewPath = newSavePath + }); + } + + public string GetWorkspacePath() + { + return _workspaceService.WorkspacePath; + } + + public string GetWorkspaceRelativePath(string filePath) + { + return _workspaceService.GetRelativePath(filePath); + } + + public async Task ShowInformationMessageAsync(string message) + { + await _languageServer.SendRequest("editor/showInformationMessage", message); + } + + public async Task ShowErrorMessageAsync(string message) + { + await _languageServer.SendRequest("editor/showErrorMessage", message); + } + + public async Task ShowWarningMessageAsync(string message) + { + await _languageServer.SendRequest("editor/showWarningMessage", message); + } + + public async Task SetStatusBarMessageAsync(string message, int? timeout) + { + await _languageServer.SendRequest("editor/setStatusBarMessage", new StatusBarMessageDetails + { + Message = message, + Timeout = timeout + }); + } + } +} diff --git a/src/PowerShellEditorServices/Extensions/ExtensionService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs similarity index 77% rename from src/PowerShellEditorServices/Extensions/ExtensionService.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs index cc4e01af4..4399a5eb9 100644 --- a/src/PowerShellEditorServices/Extensions/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs @@ -3,16 +3,14 @@ // 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 Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; 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 +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Provides a high-level service which enables PowerShell scripts @@ -25,6 +23,8 @@ public class ExtensionService private Dictionary editorCommands = new Dictionary(); + private readonly ILanguageServer _languageServer; + #endregion #region Properties @@ -44,7 +44,7 @@ public class ExtensionService /// /// Gets the PowerShellContext in which extension code will be executed. /// - public PowerShellContext PowerShellContext { get; private set; } + public PowerShellContextService PowerShellContext { get; private set; } #endregion @@ -55,9 +55,10 @@ public class ExtensionService /// PowerShellContext for loading and executing extension code. /// /// A PowerShellContext used to execute extension code. - public ExtensionService(PowerShellContext powerShellContext) + public ExtensionService(PowerShellContextService powerShellContext, ILanguageServer languageServer) { this.PowerShellContext = powerShellContext; + _languageServer = languageServer; } #endregion @@ -69,17 +70,21 @@ public ExtensionService(PowerShellContext powerShellContext) /// implementation for future interaction with the host editor. /// /// An IEditorOperations implementation. - /// An IComponentRegistry instance which provides components in the session. /// A Task that can be awaited for completion. public async Task InitializeAsync( - IEditorOperations editorOperations, - IComponentRegistry componentRegistry) + IServiceProvider serviceProvider, + IEditorOperations editorOperations) { + // Attach to ExtensionService events + this.CommandAdded += ExtensionService_ExtensionAddedAsync; + this.CommandUpdated += ExtensionService_ExtensionUpdatedAsync; + this.CommandRemoved += ExtensionService_ExtensionRemovedAsync; + this.EditorObject = new EditorObject( + serviceProvider, this, - editorOperations, - componentRegistry); + editorOperations); // Register the editor object in the runspace PSCommand variableCommand = new PSCommand(); @@ -180,6 +185,7 @@ public EditorCommand[] GetCommands() this.editorCommands.Values.CopyTo(commands,0); return commands; } + #endregion #region Events @@ -214,7 +220,34 @@ private void OnCommandRemoved(EditorCommand command) this.CommandRemoved?.Invoke(this, command); } + private void ExtensionService_ExtensionAddedAsync(object sender, EditorCommand e) + { + _languageServer.SendNotification("powerShell/extensionCommandAdded", + new ExtensionCommandAddedNotification + { + Name = e.Name, + DisplayName = e.DisplayName + }); + } + + private void ExtensionService_ExtensionUpdatedAsync(object sender, EditorCommand e) + { + _languageServer.SendNotification("powerShell/extensionCommandUpdated", + new ExtensionCommandUpdatedNotification + { + Name = e.Name, + }); + } + + private void ExtensionService_ExtensionRemovedAsync(object sender, EditorCommand e) + { + _languageServer.SendNotification("powerShell/extensionCommandRemoved", + new ExtensionCommandRemovedNotification + { + Name = e.Name, + }); + } + #endregion } } - diff --git a/src/PowerShellEditorServices/Extensions/EditorCommand.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorCommand.cs similarity index 97% rename from src/PowerShellEditorServices/Extensions/EditorCommand.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorCommand.cs index 8a7b80cef..7f24d04a2 100644 --- a/src/PowerShellEditorServices/Extensions/EditorCommand.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorCommand.cs @@ -5,7 +5,7 @@ using System.Management.Automation; -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides details about a command that has been registered diff --git a/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorCommandAttribute.cs similarity index 91% rename from src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorCommandAttribute.cs index 8020cb844..59aa71df3 100644 --- a/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorCommandAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides an attribute that can be used to target PowerShell @@ -30,4 +30,4 @@ public class EditorCommandAttribute : Attribute #endregion } -} \ No newline at end of file +} diff --git a/src/PowerShellEditorServices/Extensions/EditorContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorContext.cs similarity index 96% rename from src/PowerShellEditorServices/Extensions/EditorContext.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorContext.cs index c669acd19..144c7db53 100644 --- a/src/PowerShellEditorServices/Extensions/EditorContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorContext.cs @@ -6,8 +6,9 @@ using System; using System.Linq; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides context for the host editor at the time of creation. @@ -114,4 +115,3 @@ public void SetSelection(BufferRange selectionRange) #endregion } } - diff --git a/src/PowerShellEditorServices/Extensions/EditorObject.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorObject.cs similarity index 67% rename from src/PowerShellEditorServices/Extensions/EditorObject.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorObject.cs index 0013da684..e873cbe0d 100644 --- a/src/PowerShellEditorServices/Extensions/EditorObject.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorObject.cs @@ -3,12 +3,10 @@ // 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 +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides the entry point of the extensibility API, inserted into @@ -18,8 +16,9 @@ public class EditorObject { #region Private Fields - private ExtensionService extensionService; - private IEditorOperations editorOperations; + private readonly IServiceProvider _serviceProvider; + private readonly ExtensionService _extensionService; + private readonly IEditorOperations _editorOperations; #endregion @@ -44,10 +43,9 @@ public Version EditorServicesVersion public EditorWindow Window { get; private set; } /// - /// Gets the component registry for the session. + /// Gets the components that are registered. /// - /// - public IComponentRegistry Components { get; private set; } + public IServiceProvider Components => _serviceProvider; #endregion @@ -56,19 +54,18 @@ public Version EditorServicesVersion /// /// An ExtensionService which handles command registration. /// An IEditorOperations implementation which handles operations in the host editor. - /// An IComponentRegistry instance which provides components in the session. public EditorObject( + IServiceProvider serviceProvider, ExtensionService extensionService, - IEditorOperations editorOperations, - IComponentRegistry componentRegistry) + IEditorOperations editorOperations) { - this.extensionService = extensionService; - this.editorOperations = editorOperations; - this.Components = componentRegistry; + this._serviceProvider = serviceProvider; + this._extensionService = extensionService; + this._editorOperations = editorOperations; // Create API area objects - this.Workspace = new EditorWorkspace(this.editorOperations); - this.Window = new EditorWindow(this.editorOperations); + this.Workspace = new EditorWorkspace(this._editorOperations); + this.Window = new EditorWindow(this._editorOperations); } /// @@ -78,7 +75,7 @@ public EditorObject( /// True if the command is newly registered, false if the command already exists. public bool RegisterCommand(EditorCommand editorCommand) { - return this.extensionService.RegisterCommand(editorCommand); + return this._extensionService.RegisterCommand(editorCommand); } /// @@ -87,7 +84,7 @@ public bool RegisterCommand(EditorCommand editorCommand) /// The name of the command to be unregistered. public void UnregisterCommand(string commandName) { - this.extensionService.UnregisterCommand(commandName); + this._extensionService.UnregisterCommand(commandName); } /// @@ -96,7 +93,7 @@ public void UnregisterCommand(string commandName) /// An Array of all registered EditorCommands. public EditorCommand[] GetCommands() { - return this.extensionService.GetCommands(); + return this._extensionService.GetCommands(); } /// /// Gets the EditorContext which contains the state of the editor @@ -105,8 +102,16 @@ public EditorCommand[] GetCommands() /// A instance of the EditorContext class. public EditorContext GetEditorContext() { - return this.editorOperations.GetEditorContextAsync().Result; + return this._editorOperations.GetEditorContextAsync().Result; + } + + /// + /// Get's the desired service which allows for advanced control of PowerShellEditorServices. + /// + /// The singleton service object of the type requested. + public object GetService(Type type) + { + return _serviceProvider.GetService(type); } } } - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorRequests.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorRequests.cs new file mode 100644 index 000000000..bd639f249 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorRequests.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +{ + public class ExtensionCommandAddedNotification + { + public string Name { get; set; } + + public string DisplayName { get; set; } + } + + public class ExtensionCommandUpdatedNotification + { + public string Name { get; set; } + } + + public class ExtensionCommandRemovedNotification + { + public string Name { get; set; } + } + + + public class GetEditorContextRequest + {} + + public enum EditorCommandResponse + { + Unsupported, + OK + } + + public class InsertTextRequest + { + public string FilePath { get; set; } + + public string InsertText { get; set; } + + public Range InsertRange { get; set; } + } + + public class SetSelectionRequest + { + public Range SelectionRange { get; set; } + } + + public class SetCursorPositionRequest + { + public Position CursorPosition { get; set; } + } + + public class OpenFileDetails + { + public string FilePath { get; set; } + + public bool Preview { get; set; } + } + + public class SaveFileDetails + { + public string FilePath { get; set; } + + public string NewPath { get; set; } + } + + public class StatusBarMessageDetails + { + public string Message { get; set; } + + public int? Timeout { get; set; } + } +} diff --git a/src/PowerShellEditorServices/Extensions/EditorWindow.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorWindow.cs similarity index 97% rename from src/PowerShellEditorServices/Extensions/EditorWindow.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorWindow.cs index 8d241559a..f21a574ea 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWindow.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorWindow.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a PowerShell-facing API which allows scripts to diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorWorkspace.cs similarity index 96% rename from src/PowerShellEditorServices/Extensions/EditorWorkspace.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorWorkspace.cs index ad679f371..974d166e7 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/EditorWorkspace.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a PowerShell-facing API which allows scripts to @@ -73,4 +73,3 @@ public void OpenFile(string filePath, bool preview) #endregion } } - diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/FileContext.cs similarity index 98% rename from src/PowerShellEditorServices/Extensions/FileContext.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/FileContext.cs index 3fc5ac120..43023de24 100644 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/FileContext.cs @@ -5,10 +5,10 @@ using System; using System.IO; -using System.Linq; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides context for a file that is open in the editor. @@ -278,4 +278,3 @@ public void SaveAs(string newFilePath) #endregion } } - diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/IEditorOperations.cs similarity index 97% rename from src/PowerShellEditorServices/Extensions/IEditorOperations.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Extensions/IEditorOperations.cs index 7ef6bdf0e..15a167203 100644 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Extensions/IEditorOperations.cs @@ -4,8 +4,9 @@ // using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices.Extensions +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides an interface that must be implemented by an editor @@ -125,4 +126,3 @@ public interface IEditorOperations Task SetStatusBarMessageAsync(string message, int? timeout); } } - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs new file mode 100644 index 000000000..f0579667c --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs @@ -0,0 +1,39 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class EvaluateHandler : IEvaluateHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + + public EvaluateHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + } + + public async Task Handle(EvaluateRequestArguments request, CancellationToken cancellationToken) + { + await _powerShellContextService.ExecuteScriptStringAsync( + request.Expression, + writeInputToHost: true, + writeOutputToHost: true, + addToHistory: true); + + return new EvaluateResponseBody + { + Result = "", + VariablesReference = 0 + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs new file mode 100644 index 000000000..207b3c33a --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs @@ -0,0 +1,82 @@ +// +// 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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/expandAlias")] + public interface IExpandAliasHandler : IJsonRpcRequestHandler { } + + public class ExpandAliasParams : IRequest + { + public string Text { get; set; } + } + + public class ExpandAliasResult + { + public string Text { get; set; } + } + + public class ExpandAliasHandler : IExpandAliasHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + + public ExpandAliasHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + } + + public async Task Handle(ExpandAliasParams request, CancellationToken cancellationToken) + { + const string 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 +}"; + + // TODO: Refactor to not rerun the function definition every time. + var psCommand = new PSCommand(); + psCommand + .AddScript(script) + .AddStatement() + .AddCommand("__Expand-Alias") + .AddArgument(request.Text); + var result = await _powerShellContextService.ExecuteCommandAsync(psCommand); + + return new ExpandAliasResult + { + Text = result.First() + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs new file mode 100644 index 000000000..56c39f5b8 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs @@ -0,0 +1,81 @@ +// +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/getCommand")] + public interface IGetCommandHandler : IJsonRpcRequestHandler> { } + + public class GetCommandParams : IRequest> { } + + /// + /// 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; } + } + + public class GetCommandHandler : IGetCommandHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + + public GetCommandHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + } + + public async Task> Handle(GetCommandParams request, CancellationToken cancellationToken) + { + PSCommand psCommand = new PSCommand(); + + // 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 _powerShellContextService.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 + }); + } + } + + return commandList; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommentHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommentHelpHandler.cs new file mode 100644 index 000000000..1f79582a1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommentHelpHandler.cs @@ -0,0 +1,103 @@ +// +// 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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class GetCommentHelpHandler : IGetCommentHelpHandler + { + private readonly ILogger _logger; + private readonly WorkspaceService _workspaceService; + private readonly AnalysisService _analysisService; + private readonly SymbolsService _symbolsService; + + public GetCommentHelpHandler( + ILoggerFactory factory, + WorkspaceService workspaceService, + AnalysisService analysisService, + SymbolsService symbolsService) + { + _logger = factory.CreateLogger(); + _workspaceService = workspaceService; + _analysisService = analysisService; + _symbolsService = symbolsService; + } + + public async Task Handle(CommentHelpRequestParams request, CancellationToken cancellationToken) + { + var result = new CommentHelpRequestResult(); + + if (!_workspaceService.TryGetFile(request.DocumentUri, out ScriptFile scriptFile)) + { + return result; + } + + int triggerLine = (int) request.TriggerPosition.Line + 1; + + FunctionDefinitionAst functionDefinitionAst = _symbolsService.GetFunctionDefinitionForHelpComment( + scriptFile, + triggerLine, + out string helpLocation); + + if (functionDefinitionAst == null) + { + return result; + } + + 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.GetLinesInternal(funcText); + int relativeTriggerLine0b = triggerLine - funcExtent.StartLineNumber; + if (relativeTriggerLine0b > 0 && lines[relativeTriggerLine0b].IndexOf("<", StringComparison.OrdinalIgnoreCase) > -1) + { + lines[relativeTriggerLine0b] = string.Empty; + } + + funcText = string.Join("\n", lines); + } + + List analysisResults = await _analysisService.GetSemanticMarkersAsync( + funcText, + AnalysisService.GetCommentHelpRuleSettings( + enable: true, + exportedOnly: false, + blockComment: request.BlockComment, + vscodeSnippetCorrection: true, + placement: helpLocation)); + + string helpText = analysisResults?.FirstOrDefault()?.Correction?.Edits[0].Text; + + if (helpText == null) + { + return result; + } + + result.Content = ScriptFile.GetLinesInternal(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(); + } + + return result; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs new file mode 100644 index 000000000..ad8fd62d1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs @@ -0,0 +1,56 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Utility; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class GetVersionHandler : IGetVersionHandler + { + private readonly ILogger _logger; + + public GetVersionHandler(ILoggerFactory factory) + { + _logger = factory.CreateLogger(); + } + + public Task Handle(GetVersionParams request, CancellationToken cancellationToken) + { + var architecture = PowerShellProcessArchitecture.Unknown; + // This should be changed to using a .NET call sometime in the future... but it's just for logging purposes. + string arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + if (arch != null) + { + if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase)) + { + architecture = PowerShellProcessArchitecture.X64; + } + else if (string.Equals(arch, "x86", StringComparison.CurrentCultureIgnoreCase)) + { + architecture = PowerShellProcessArchitecture.X86; + } + } + + return Task.FromResult(new PowerShellVersion + { + Version = VersionUtils.PSVersion.ToString(), + Edition = VersionUtils.PSEdition, + DisplayVersion = VersionUtils.PSVersion.ToString(2), + Architecture = architecture.ToString() + }); + } + + private enum PowerShellProcessArchitecture + { + Unknown, + X86, + X64 + } + } +} diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/EvaluateRequest.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IEvaluateHandler.cs similarity index 73% rename from src/PowerShellEditorServices.Protocol/DebugAdapter/EvaluateRequest.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IEvaluateHandler.cs index d45e7d44a..b2836af9a 100644 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/EvaluateRequest.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IEvaluateHandler.cs @@ -1,20 +1,17 @@ -// +// // 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 OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; -namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter +namespace Microsoft.PowerShell.EditorServices.Handlers { - public class EvaluateRequest - { - public static readonly - RequestType Type = - RequestType.Create("evaluate"); - } + [Serial, Method("evaluate")] + public interface IEvaluateHandler : IJsonRpcRequestHandler { } - public class EvaluateRequestArguments + public class EvaluateRequestArguments : IRequest { /// /// The expression to evaluate. @@ -50,4 +47,3 @@ public class EvaluateResponseBody public int VariablesReference { get; set; } } } - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetCommentHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetCommentHelpHandler.cs new file mode 100644 index 000000000..599e13df5 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetCommentHelpHandler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/getCommentHelp")] + public interface IGetCommentHelpHandler : IJsonRpcRequestHandler { } + + public class CommentHelpRequestResult + { + public string[] Content { get; set; } + } + + public class CommentHelpRequestParams : IRequest + { + public string DocumentUri { get; set; } + public Position TriggerPosition { get; set; } + public bool BlockComment { get; set; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs new file mode 100644 index 000000000..88df2a429 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/getPSHostProcesses")] + public interface IGetPSHostProcessesHandler : IJsonRpcRequestHandler { } + + public class GetPSHostProcesssesParams : IRequest { } + + public class PSHostProcessResponse + { + 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/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs new file mode 100644 index 000000000..77237dee1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/getRunspace")] + public interface IGetRunspaceHandler : IJsonRpcRequestHandler { } + + public class GetRunspaceParams : IRequest + { + public string ProcessId {get; set; } + } + + public class RunspaceResponse + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Availability { get; set; } + } +} diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/PowerShellVersionRequest.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetVersionHandler.cs similarity index 66% rename from src/PowerShellEditorServices.Protocol/LanguageServer/PowerShellVersionRequest.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetVersionHandler.cs index 8f16b0abb..1810435a2 100644 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/PowerShellVersionRequest.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetVersionHandler.cs @@ -1,28 +1,24 @@ -// +// // 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; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; -namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer +namespace Microsoft.PowerShell.EditorServices.Handlers { - public class PowerShellVersionRequest - { - public static readonly - RequestType Type = - RequestType.Create("powerShell/getVersion"); - } + [Serial, Method("powerShell/getVersion")] + public interface IGetVersionHandler : IJsonRpcRequestHandler { } + + public class GetVersionParams : IRequest { } 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() @@ -32,7 +28,7 @@ public PowerShellVersion() public PowerShellVersion(PowerShellVersionDetails versionDetails) { this.Version = versionDetails.VersionString; - this.DisplayVersion = $"{versionDetails.Version.Major}.{versionDetails.Version.Minor}"; + this.DisplayVersion = $"{versionDetails.Version.Major}.{versionDetails.Version.Minor}"; this.Edition = versionDetails.Edition; switch (versionDetails.Architecture) diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs new file mode 100644 index 000000000..42e8b72ed --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/invokeExtensionCommand")] + public interface IInvokeExtensionCommandHandler : IJsonRpcNotificationHandler { } + + public class InvokeExtensionCommandParams : IRequest + { + public string Name { get; set; } + + public ClientEditorContext Context { get; set; } + } + + public class ClientEditorContext + { + public string CurrentFileContent { get; set; } + + public string CurrentFileLanguage { get; set; } + + public string CurrentFilePath { get; set; } + + public Position CursorPosition { get; set; } + + public Range SelectionRange { get; set; } + + } +} diff --git a/src/PowerShellEditorServices/Templates/TemplateDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ITemplateHandlers.cs similarity index 50% rename from src/PowerShellEditorServices/Templates/TemplateDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ITemplateHandlers.cs index c15d58e29..798f0c9fe 100644 --- a/src/PowerShellEditorServices/Templates/TemplateDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ITemplateHandlers.cs @@ -3,8 +3,29 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices.Templates +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers { + [Serial, Method("powerShell/getProjectTemplates")] + public interface IGetProjectTemplatesHandler : IJsonRpcRequestHandler { } + + [Serial, Method("powerShell/newProjectFromTemplate")] + public interface INewProjectFromTemplateHandler : IJsonRpcRequestHandler { } + + public class GetProjectTemplatesRequest : IRequest + { + public bool IncludeInstalledModules { get; set; } + } + + public class GetProjectTemplatesResponse + { + public bool NeedsModuleInstall { get; set; } + + public TemplateDetails[] Templates { get; set; } + } + /// /// Provides details about a file or project template. /// @@ -40,4 +61,16 @@ public class TemplateDetails /// public string TemplatePath { get; set; } } + + public class NewProjectFromTemplateRequest : IRequest + { + public string DestinationPath { get; set; } + + public string TemplatePath { get; set; } + } + + public class NewProjectFromTemplateResponse + { + public bool CreationSuccessful { get; set; } + } } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs new file mode 100644 index 000000000..355a3d3db --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs @@ -0,0 +1,46 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using OmniSharp.Extensions.Embedded.MediatR; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class InvokeExtensionCommandHandler : IInvokeExtensionCommandHandler + { + private readonly ILogger _logger; + private readonly ExtensionService _extensionService; + private readonly EditorOperationsService _editorOperationsService; + + public InvokeExtensionCommandHandler( + ILoggerFactory factory, + ExtensionService extensionService, + EditorOperationsService editorOperationsService + ) + { + _logger = factory.CreateLogger(); + _extensionService = extensionService; + _editorOperationsService = editorOperationsService; + } + + public async Task Handle(InvokeExtensionCommandParams request, CancellationToken cancellationToken) + { + // We can now await here because we handle asynchronous message handling. + EditorContext editorContext = + _editorOperationsService.ConvertClientEditorContext( + request.Context); + + await _extensionService.InvokeCommandAsync( + request.Name, + editorContext); + + return await Unit.Task; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs new file mode 100644 index 000000000..9f82983ad --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -0,0 +1,114 @@ +// +// 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.Runspaces; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + using System.Management.Automation; + + public class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + + public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + } + + public Task Handle(GetPSHostProcesssesParams request, CancellationToken cancellationToken) + { + var psHostProcesses = new List(); + + int processId = System.Diagnostics.Process.GetCurrentProcess().Id; + + using (var pwsh = PowerShell.Create()) + { + pwsh.AddCommand("Get-PSHostProcessInfo") + .AddCommand("Where-Object") + .AddParameter("Property", "ProcessId") + .AddParameter("NE") + .AddParameter("Value", processId.ToString()); + + var processes = pwsh.Invoke(); + + if (processes != null) + { + foreach (dynamic p in processes) + { + psHostProcesses.Add( + new PSHostProcessResponse + { + ProcessName = p.ProcessName, + ProcessId = p.ProcessId, + AppDomainName = p.AppDomainName, + MainWindowTitle = p.MainWindowTitle + }); + } + } + } + + return Task.FromResult(psHostProcesses.ToArray()); + } + + public async Task Handle(GetRunspaceParams request, CancellationToken cancellationToken) + { + IEnumerable runspaces = null; + + if (request.ProcessId == null) + { + request.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(request.ProcessId, out int pid)) + { + // Create a remote runspace that we will invoke Get-Runspace in. + using (var rs = RunspaceFactory.CreateRunspace(new NamedPipeConnectionInfo(pid))) + using (var ps = PowerShell.Create()) + { + 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 _powerShellContextService.ExecuteCommandAsync(psCommand, sb); + } + + var runspaceResponses = new List(); + + if (runspaces != null) + { + foreach (dynamic runspace in runspaces) + { + runspaceResponses.Add( + new RunspaceResponse + { + Id = runspace.Id, + Name = runspace.Name, + Availability = runspace.RunspaceAvailability.ToString() + }); + } + } + + return runspaceResponses.ToArray(); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs new file mode 100644 index 000000000..897d6f4ae --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs @@ -0,0 +1,81 @@ +// +// 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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/showHelp")] + public interface IShowHelpHandler : IJsonRpcNotificationHandler { } + + public class ShowHelpParams : IRequest + { + public string Text { get; set; } + } + + public class ShowHelpHandler : IShowHelpHandler + { + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + + public ShowHelpHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + } + + public async Task Handle(ShowHelpParams request, CancellationToken cancellationToken) + { + 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 + "; + + string helpParams = request.Text; + 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 _powerShellContextService.ExecuteCommandAsync(checkHelpPSCommand, sendOutputToHost: true); + return Unit.Value; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/TemplateHandlers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/TemplateHandlers.cs new file mode 100644 index 000000000..210ff3ca3 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/TemplateHandlers.cs @@ -0,0 +1,73 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class TemplateHandlers : IGetProjectTemplatesHandler, INewProjectFromTemplateHandler + { + private readonly ILogger _logger; + private readonly TemplateService _templateService; + + public TemplateHandlers( + ILoggerFactory factory, + TemplateService templateService) + { + _logger = factory.CreateLogger(); + _templateService = templateService; + } + + public async Task Handle(GetProjectTemplatesRequest request, CancellationToken cancellationToken) + { + bool plasterInstalled = await _templateService.ImportPlasterIfInstalledAsync(); + + if (plasterInstalled) + { + var availableTemplates = + await _templateService.GetAvailableTemplatesAsync( + request.IncludeInstalledModules); + + + return new GetProjectTemplatesResponse + { + Templates = availableTemplates + }; + } + + return new GetProjectTemplatesResponse + { + NeedsModuleInstall = true, + Templates = new TemplateDetails[0] + }; + } + + public async Task Handle(NewProjectFromTemplateRequest request, CancellationToken cancellationToken) + { + bool creationSuccessful; + try + { + await _templateService.CreateFromTemplateAsync(request.TemplatePath, request.DestinationPath); + creationSuccessful = true; + } + catch (Exception e) + { + // We don't really care if this worked or not but we report status. + _logger.LogException("New plaster template failed.", e); + creationSuccessful = false; + } + + return new NewProjectFromTemplateResponse + { + CreationSuccessful = creationSuccessful + }; + } + } +} diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs similarity index 92% rename from src/PowerShellEditorServices/Session/PowerShellContext.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs index fbfe46cb2..dfe550c18 100644 --- a/src/PowerShellEditorServices/Session/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -5,41 +5,41 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.ComponentModel; 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.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services { - using System.ComponentModel; using System.Management.Automation; + using Microsoft.PowerShell.EditorServices.Handlers; + using Microsoft.PowerShell.EditorServices.Hosting; + using Microsoft.PowerShell.EditorServices.Logging; + using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; /// /// 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 + public class PowerShellContextService : IDisposable, IHostSupportsInteractiveSession { private static readonly Action s_runspaceApartmentStateSetter; - static PowerShellContext() + static PowerShellContextService() { // PowerShell ApartmentState APIs aren't available in PSStandard, so we need to use reflection - if (!Utils.IsNetCore) + if (!VersionUtils.IsNetCore || VersionUtils.IsPS7) { MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); @@ -51,6 +51,7 @@ static PowerShellContext() private readonly SemaphoreSlim resumeRequestHandle = AsyncUtils.CreateSimpleLockingSemaphore(); + private readonly OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer _languageServer; private bool isPSReadLineEnabled; private ILogger logger; private PowerShell powerShell; @@ -113,7 +114,7 @@ public PowerShellVersionDetails LocalPowerShellVersion /// private IHostOutput ConsoleWriter { get; set; } - private IHostInput ConsoleReader { get; set; } + internal IHostInput ConsoleReader { get; private set; } /// /// Gets details pertaining to the current runspace. @@ -136,6 +137,10 @@ public RunspaceDetails CurrentRunspace /// public string InitialWorkingDirectory { get; private set; } + internal bool IsDebugServerActive { get; set; } + + internal DebuggerStopEventArgs CurrentDebuggerStopEventArgs { get; private set; } + #endregion #region Constructors @@ -147,10 +152,75 @@ public RunspaceDetails CurrentRunspace /// /// Indicates whether PSReadLine should be used if possible /// - public PowerShellContext(ILogger logger, bool isPSReadLineEnabled) + public PowerShellContextService( + ILogger logger, + OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer languageServer, + bool isPSReadLineEnabled) { + _languageServer = languageServer; this.logger = logger; this.isPSReadLineEnabled = isPSReadLineEnabled; + + RunspaceChanged += PowerShellContext_RunspaceChangedAsync; + ExecutionStatusChanged += PowerShellContext_ExecutionStatusChangedAsync; + } + + public static PowerShellContextService Create( + ILoggerFactory factory, + OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer languageServer, + ProfilePaths profilePaths, + HashSet featureFlags, + bool enableConsoleRepl, + PSHost internalHost, + HostDetails hostDetails, + string[] additionalModules + ) + { + var logger = factory.CreateLogger(); + + // PSReadLine can only be used when -EnableConsoleRepl is specified otherwise + // issues arise when redirecting stdio. + var powerShellContext = new PowerShellContextService( + logger, + languageServer, + featureFlags.Contains("PSReadLine") && enableConsoleRepl); + + EditorServicesPSHostUserInterface hostUserInterface = + enableConsoleRepl + ? (EditorServicesPSHostUserInterface)new TerminalPSHostUserInterface(powerShellContext, logger, internalHost) + : new ProtocolPSHostUserInterface(languageServer, powerShellContext, logger); + + EditorServicesPSHost psHost = + new EditorServicesPSHost( + powerShellContext, + hostDetails, + hostUserInterface, + logger); + + Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost); + powerShellContext.Initialize(profilePaths, initialRunspace, true, hostUserInterface); + + powerShellContext.ImportCommandsModuleAsync( + Path.Combine( + Path.GetDirectoryName(typeof(PowerShellContextService).GetTypeInfo().Assembly.Location), + @"..\Commands")); + + // 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 additionalModules) + { + var command = + new PSCommand() + .AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("Name", module); + + powerShellContext.ExecuteCommandAsync( + command, + sendOutputToHost: false, + sendErrorToHost: true); + } + + return powerShellContext; } /// @@ -165,7 +235,7 @@ public PowerShellContext(ILogger logger, bool isPSReadLineEnabled) /// public static Runspace CreateRunspace( HostDetails hostDetails, - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, EditorServicesPSHostUserInterface hostUserInterface, ILogger logger) { @@ -260,8 +330,7 @@ public void Initialize( this.CurrentRunspace = this.initialRunspace; // Write out the PowerShell version for tracking purposes - this.logger.Write( - LogLevel.Normal, + this.logger.LogInformation( string.Format( "PowerShell runtime version: {0}, edition: {1}", this.LocalPowerShellVersion.Version, @@ -537,7 +606,7 @@ public async Task> ExecuteCommandAsync( this.ShouldExecuteWithEventing(executionOptions) || (PromptNest.IsRemote && executionOptions.IsReadLine))) { - this.logger.Write(LogLevel.Verbose, "Passing command execution to pipeline thread."); + this.logger.LogTrace("Passing command execution to pipeline thread."); if (shouldCancelReadLine && PromptNest.IsReadLineBusy()) { @@ -620,8 +689,7 @@ public async Task> ExecuteCommandAsync( } catch (Exception e) { - logger.Write( - LogLevel.Error, + logger.LogError( "Exception occurred while executing debugger command:\r\n\r\n" + e.ToString()); } finally @@ -643,8 +711,7 @@ public async Task> ExecuteCommandAsync( AddToHistory = executionOptions.AddToHistory }; - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( "Attempting to execute command(s):\r\n\r\n{0}", GetStringForPSCommand(psCommand))); @@ -713,38 +780,34 @@ public async Task> ExecuteCommandAsync( var errorMessage = strBld.ToString(); errorMessages?.Append(errorMessage); - this.logger.Write(LogLevel.Error, errorMessage); + this.logger.LogError(errorMessage); hadErrors = true; } else { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( "Execution completed successfully."); } } } catch (PSRemotingDataStructureException e) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "Pipeline stopped while executing command:\r\n\r\n" + e.ToString()); errorMessages?.Append(e.Message); } catch (PipelineStoppedException e) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "Pipeline stopped while executing command:\r\n\r\n" + e.ToString()); errorMessages?.Append(e.Message); } catch (RuntimeException e) { - this.logger.Write( - LogLevel.Warning, + this.logger.LogWarning( "Runtime exception occurred while executing command:\r\n\r\n" + e.ToString()); hadErrors = true; @@ -756,7 +819,7 @@ public async Task> ExecuteCommandAsync( this.WriteExceptionToHost(e); } } - catch (Exception e) + catch (Exception) { this.OnExecutionStatusChanged( ExecutionStatus.Failed, @@ -954,8 +1017,7 @@ public async Task ExecuteScriptWithArgsAsync(string script, string arguments = n } catch (System.Management.Automation.DriveNotFoundException e) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "Could not determine current filesystem location:\r\n\r\n" + e.ToString()); } @@ -982,7 +1044,7 @@ public async Task ExecuteScriptWithArgsAsync(string script, string arguments = n strBld.Append(' ').Append(arguments); var launchedScript = strBld.ToString(); - this.logger.Write(LogLevel.Verbose, $"Launch script is: {launchedScript}"); + this.logger.LogTrace($"Launch script is: {launchedScript}"); command.AddScript(launchedScript, false); } @@ -1079,10 +1141,9 @@ 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(); + PSCommand command = new PSCommand(); command.AddCommand(profilePath, false); await this.ExecuteCommandAsync(command, true, true); } @@ -1115,7 +1176,7 @@ public void AbortExecution(bool shouldAbortDebugSession) if (this.SessionState != PowerShellContextState.Aborting && this.SessionState != PowerShellContextState.Disposed) { - this.logger.Write(LogLevel.Verbose, "Execution abort requested..."); + this.logger.LogTrace("Execution abort requested..."); if (shouldAbortDebugSession) { @@ -1148,8 +1209,7 @@ public void AbortExecution(bool shouldAbortDebugSession) } else { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( $"Execution abort requested when already aborted (SessionState = {this.SessionState})")); } @@ -1189,7 +1249,7 @@ internal async Task ExitAllNestedPromptsAsync() /// internal void BreakExecution() { - this.logger.Write(LogLevel.Verbose, "Debugger break requested..."); + this.logger.LogTrace("Debugger break requested..."); // Pause the debugger this.versionSpecificOperations.PauseDebugger( @@ -1240,8 +1300,7 @@ private void ResumeDebugger(DebuggerResumeAction resumeAction, bool shouldWaitFo } else { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( $"Tried to resume debugger with action {resumeAction} but there was no debuggerStoppedTask."); } } @@ -1392,8 +1451,7 @@ private void CloseRunspace(RunspaceDetails runspaceDetails) if (exitException != null) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( $"Caught {exitException.GetType().Name} while exiting {runspaceDetails.Location} runspace:\r\n{exitException.ToString()}"); } } @@ -1412,8 +1470,7 @@ internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) else { // Write the situation to the log since this shouldn't happen - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "ReleaseRunspaceHandle was called when the main thread was not busy."); } } @@ -1486,8 +1543,7 @@ internal void ExitNestedPrompt() { if (this.PromptNest.NestedPromptLevel == 1 || !this.PromptNest.IsNestedPrompt) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "ExitNestedPrompt was called outside of a nested prompt."); return; } @@ -1690,8 +1746,7 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e { if (this.SessionState != PowerShellContextState.Disposed) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( "Session state changed --\r\n\r\n Old state: {0}\r\n New state: {1}\r\n Result: {2}", this.SessionState.ToString(), @@ -1703,8 +1758,7 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e } else { - this.logger.Write( - LogLevel.Warning, + this.logger.LogWarning( $"Received session state change to {e.NewSessionState} when already disposed"); } } @@ -1737,14 +1791,58 @@ private void OnExecutionStatusChanged( hadErrors)); } + private void PowerShellContext_RunspaceChangedAsync(object sender, RunspaceChangedEventArgs e) + { + _languageServer.SendNotification( + "powerShell/runspaceChanged", + new MinifiedRunspaceDetails(e.NewRunspace)); + } + + + // TODO: Refactor this, RunspaceDetails, PowerShellVersion, and PowerShellVersionDetails + // It's crazy that this is 4 different types. + // P.S. MinifiedRunspaceDetails use to be called RunspaceDetails... as in, there were 2 DIFFERENT + // RunspaceDetails types in this codebase but I've changed it to be minified since the type is + // slightly simpler than the other RunspaceDetails. + public class MinifiedRunspaceDetails + { + public PowerShellVersion PowerShellVersion { get; set; } + + public RunspaceLocation RunspaceType { get; set; } + + public string ConnectionString { get; set; } + + public MinifiedRunspaceDetails() + { + } + + public MinifiedRunspaceDetails(RunspaceDetails eventArgs) + { + this.PowerShellVersion = new PowerShellVersion(eventArgs.PowerShellVersion); + this.RunspaceType = eventArgs.Location; + this.ConnectionString = eventArgs.ConnectionString; + } + } + + /// + /// 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 void PowerShellContext_ExecutionStatusChangedAsync(object sender, ExecutionStatusChangedEventArgs e) + { + _languageServer.SendNotification( + "powerShell/executionStatusChanged", + e); + } + #endregion #region Private Methods private IEnumerable ExecuteCommandInDebugger(PSCommand psCommand, bool sendOutputToHost) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( "Attempting to execute command(s) in the debugger:\r\n\r\n{0}", GetStringForPSCommand(psCommand))); @@ -1981,8 +2079,7 @@ private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) desiredExecutionPolicy == ExecutionPolicy.Bypass || currentPolicy == ExecutionPolicy.Undefined) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( "Setting execution policy:\r\n Current = ExecutionPolicy.{0}\r\n Desired = ExecutionPolicy.{1}", currentPolicy, @@ -2001,7 +2098,7 @@ private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) } catch (CmdletInvocationException e) { - this.logger.WriteException( + this.logger.LogException( $"An error occurred while calling Set-ExecutionPolicy, the desired policy of {desiredExecutionPolicy} may not be set.", e); } @@ -2010,8 +2107,7 @@ private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) } else { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( "Current execution policy: ExecutionPolicy.{0}", currentPolicy)); @@ -2032,14 +2128,12 @@ private SessionDetails GetSessionDetails(Func invokeAction) } catch (RuntimeException e) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( "Runtime exception occurred while gathering runspace info:\r\n\r\n" + e.ToString()); } catch (ArgumentNullException) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "Could not retrieve session details but no exception was thrown."); } @@ -2132,8 +2226,7 @@ private void SetProfileVariableInCurrentRunspace(ProfilePaths profilePaths) nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost)); - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( string.Format( "Setting $profile variable in runspace. Current user host profile path: {0}", profilePaths.CurrentUserCurrentHost)); @@ -2207,12 +2300,21 @@ private void StartCommandLoopOnRunspaceAvailable() private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) { + // We maintain the current stop event args so that we can use it in the DebugServer to fire the "stopped" event + // when the DebugServer is fully started. + CurrentDebuggerStopEventArgs = e; + + if (!IsDebugServerActive) + { + _languageServer.SendNotification("powerShell/startDebugger"); + } + if (CurrentRunspace.Context == RunspaceContext.Original) { StartCommandLoopOnRunspaceAvailable(); } - this.logger.Write(LogLevel.Verbose, "Debugger stopped execution."); + this.logger.LogTrace("Debugger stopped execution."); PromptNest.PushPromptContext( IsCurrentRunspaceOutOfProcess() @@ -2238,8 +2340,7 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) } catch (InvalidOperationException) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( "Attempting to get session details failed, most likely due to a running pipeline that is attempting to stop."); } @@ -2252,7 +2353,7 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) this.DebuggerStop?.Invoke(sender, e); } - this.logger.Write(LogLevel.Verbose, "Starting pipeline thread message loop..."); + this.logger.LogTrace("Starting pipeline thread message loop..."); Task localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); @@ -2271,7 +2372,10 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) this.WriteOutput("", true); e.ResumeAction = localDebuggerStoppedTask.GetAwaiter().GetResult(); - this.logger.Write(LogLevel.Verbose, "Received debugger resume action " + e.ResumeAction.ToString()); + this.logger.LogTrace("Received debugger resume action " + e.ResumeAction.ToString()); + + // Since we are no longer at a breakpoint, we set this to null. + CurrentDebuggerStopEventArgs = null; // Notify listeners that the debugger has resumed this.DebuggerResumed?.Invoke(this, e.ResumeAction); @@ -2298,18 +2402,21 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) } else if (taskIndex == 1) { - this.logger.Write(LogLevel.Verbose, "Received pipeline thread execution request."); + this.logger.LogTrace("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."); + this.logger.LogTrace("Pipeline thread execution completed."); if (!this.versionSpecificOperations.IsDebuggerStopped( this.PromptNest, this.CurrentRunspace.Runspace)) { + // Since we are no longer at a breakpoint, we set this to null. + CurrentDebuggerStopEventArgs = null; + if (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace) { // Notify listeners that the debugger has resumed @@ -2353,8 +2460,7 @@ private void ConfigureRunspaceCapabilities(RunspaceDetails runspaceDetails) private void PushRunspace(RunspaceDetails newRunspaceDetails) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( $"Pushing {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), new runspace is {newRunspaceDetails.Location} ({newRunspaceDetails.Context}), connection: {newRunspaceDetails.ConnectionString}"); RunspaceDetails previousRunspace = this.CurrentRunspace; @@ -2456,8 +2562,7 @@ private void PopRunspace() RunspaceDetails previousRunspace = this.CurrentRunspace; this.CurrentRunspace = this.runspaceStack.Pop(); - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( $"Popping {previousRunspace.Location} ({previousRunspace.Context}), new runspace is {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), connection: {this.CurrentRunspace.ConnectionString}"); if (previousRunspace.Context == RunspaceContext.DebuggedRunspace) @@ -2483,8 +2588,7 @@ private void PopRunspace() } else { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( "Caller attempted to pop a runspace when no runspaces are on the stack."); } } diff --git a/src/PowerShellEditorServices/Session/RemoteFileManager.cs b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs similarity index 95% rename from src/PowerShellEditorServices/Session/RemoteFileManager.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs index c9885b84f..cca85a199 100644 --- a/src/PowerShellEditorServices/Session/RemoteFileManager.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs @@ -3,7 +3,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; @@ -15,20 +17,20 @@ using System.Text; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Manages files that are accessed from a remote PowerShell session. /// Also manages the registration and handling of the 'psedit' function. /// - public class RemoteFileManager + internal class RemoteFileManagerService { #region Fields private ILogger logger; private string remoteFilesPath; private string processTempPath; - private PowerShellContext powerShellContext; + private PowerShellContextService powerShellContext; private IEditorOperations editorOperations; private Dictionary filesPerComputer = @@ -235,23 +237,23 @@ function New-EditorFile { #region Constructors /// - /// Creates a new instance of the RemoteFileManager class. + /// Creates a new instance of the RemoteFileManagerService class. /// + /// An ILoggerFactory implementation used for writing log messages. /// /// 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) + public RemoteFileManagerService( + ILoggerFactory factory, + PowerShellContextService powerShellContext, + EditorOperationsService editorOperations) { Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - this.logger = logger; + this.logger = factory.CreateLogger(); this.powerShellContext = powerShellContext; this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; @@ -322,8 +324,7 @@ public async Task FetchRemoteFileAsync( } else { - this.logger.Write( - LogLevel.Warning, + this.logger.LogWarning( $"Could not load contents of remote file '{remoteFilePath}'"); } } @@ -331,8 +332,7 @@ public async Task FetchRemoteFileAsync( } catch (IOException e) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( $"Caught {e.GetType().Name} while attempting to get remote file at path '{remoteFilePath}'\r\n\r\n{e.ToString()}"); } } @@ -357,8 +357,7 @@ public async Task SaveRemoteFileAsync(string localFilePath) localFilePath, this.powerShellContext.CurrentRunspace); - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( $"Saving remote file {remoteFilePath} (local path: {localFilePath})"); byte[] localFileContents = null; @@ -368,7 +367,7 @@ public async Task SaveRemoteFileAsync(string localFilePath) } catch (IOException e) { - this.logger.WriteException( + this.logger.LogException( "Failed to read contents of local copy of remote file", e); @@ -391,7 +390,7 @@ await this.powerShellContext.ExecuteCommandAsync( if (errorMessages.Length > 0) { - this.logger.Write(LogLevel.Error, $"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); + this.logger.LogError($"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); } } @@ -422,8 +421,7 @@ public string CreateTemporaryFile(string fileName, string fileContents, Runspace } catch (IOException e) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( $"Caught {e.GetType().Name} while attempting to write temporary file at path '{temporaryFilePath}'\r\n\r\n{e.ToString()}"); temporaryFilePath = null; @@ -610,7 +608,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) } catch (NullReferenceException e) { - this.logger.WriteException("Could not store null remote file content", e); + this.logger.LogException("Could not store null remote file content", e); } } } @@ -645,7 +643,7 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) } catch (RemoteException e) { - this.logger.WriteException("Could not create psedit function.", e); + this.logger.LogException("Could not create psedit function.", e); } } } @@ -674,7 +672,7 @@ private void RemovePSEditFunction(RunspaceDetails runspaceDetails) } catch (Exception e) when (e is RemoteException || e is PSInvalidOperationException) { - this.logger.WriteException("Could not remove psedit function.", e); + this.logger.LogException("Could not remove psedit function.", e); } } } @@ -692,7 +690,7 @@ private void TryDeleteTemporaryPath() } catch (IOException e) { - this.logger.WriteException( + this.logger.LogException( $"Could not delete temporary folder for current process: {this.processTempPath}", e); } } @@ -704,7 +702,7 @@ private void TryDeleteTemporaryPath() private class RemotePathMappings { private RunspaceDetails runspaceDetails; - private RemoteFileManager remoteFileManager; + private RemoteFileManagerService remoteFileManager; private HashSet openedPaths = new HashSet(); private Dictionary pathMappings = new Dictionary(); @@ -715,7 +713,7 @@ public IEnumerable OpenedPaths public RemotePathMappings( RunspaceDetails runspaceDetails, - RemoteFileManager remoteFileManager) + RemoteFileManagerService remoteFileManager) { this.runspaceDetails = runspaceDetails; this.remoteFileManager = remoteFileManager; diff --git a/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs similarity index 86% rename from src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs index 998b58a79..64e459ecb 100644 --- a/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs @@ -6,8 +6,11 @@ using System.Linq; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Session.Capabilities +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { + using Microsoft.Extensions.Logging; + using Microsoft.PowerShell.EditorServices.Logging; + using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; @@ -22,7 +25,7 @@ internal class DscBreakpointCapability : IRunspaceCapability new Dictionary(); public async Task> SetLineBreakpointsAsync( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, string scriptPath, BreakpointDetails[] breakpoints) { @@ -79,7 +82,7 @@ public bool IsDscResourcePath(string scriptPath) public static DscBreakpointCapability CheckForCapability( RunspaceDetails runspaceDetails, - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, ILogger logger) { DscBreakpointCapability capability = null; @@ -106,12 +109,12 @@ public static DscBreakpointCapability CheckForCapability( } catch (RuntimeException e) { - logger.WriteException("Could not load the DSC module!", e); + logger.LogException("Could not load the DSC module!", e); } if (moduleInfo != null) { - logger.Write(LogLevel.Verbose, "Side-by-side DSC module found, gathering DSC resource paths..."); + logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths..."); // The module was loaded, add the breakpoint capability capability = new DscBreakpointCapability(); @@ -135,7 +138,7 @@ public static DscBreakpointCapability CheckForCapability( } catch (CmdletInvocationException e) { - logger.WriteException("Get-DscResource failed!", e); + logger.LogException("Get-DscResource failed!", e); } if (resourcePaths != null) @@ -145,16 +148,16 @@ public static DscBreakpointCapability CheckForCapability( .Select(o => (string)o.BaseObject) .ToArray(); - logger.Write(LogLevel.Verbose, $"DSC resources found: {resourcePaths.Count}"); + logger.LogTrace($"DSC resources found: {resourcePaths.Count}"); } else { - logger.Write(LogLevel.Verbose, $"No DSC resources found."); + logger.LogTrace($"No DSC resources found."); } } else { - logger.Write(LogLevel.Verbose, $"Side-by-side DSC module was not found."); + logger.LogTrace($"Side-by-side DSC module was not found."); } } } diff --git a/src/PowerShellEditorServices/Session/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs similarity index 97% rename from src/PowerShellEditorServices/Session/ExecutionOptions.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs index a1071606f..fba9736e0 100644 --- a/src/PowerShellEditorServices/Session/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Defines options for the execution of a command. diff --git a/src/PowerShellEditorServices/Session/ExecutionStatus.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs similarity index 92% rename from src/PowerShellEditorServices/Session/ExecutionStatus.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs index 233d0499e..66caef00e 100644 --- a/src/PowerShellEditorServices/Session/ExecutionStatus.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Enumerates the possible execution results that can occur after diff --git a/src/PowerShellEditorServices/Session/ExecutionStatusChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs similarity index 93% rename from src/PowerShellEditorServices/Session/ExecutionStatusChangedEventArgs.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs index cd2dcaaf2..f9abb1cbb 100644 --- a/src/PowerShellEditorServices/Session/ExecutionStatusChangedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs @@ -3,10 +3,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// - /// Contains details about an executed + /// Contains details about an executed /// public class ExecutionStatusChangedEventArgs { diff --git a/src/PowerShellEditorServices/Session/ExecutionTarget.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs similarity index 91% rename from src/PowerShellEditorServices/Session/ExecutionTarget.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs index 70ec3cb6f..19b365f76 100644 --- a/src/PowerShellEditorServices/Session/ExecutionTarget.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Represents the different API's available for executing commands. diff --git a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHost.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs similarity index 96% rename from src/PowerShellEditorServices/Session/Host/EditorServicesPSHost.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs index 7c32acdb3..39604452b 100644 --- a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs @@ -3,14 +3,14 @@ // 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 Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; using System; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides an implementation of the PSHost class for the @@ -26,7 +26,7 @@ public class EditorServicesPSHost : PSHost, IHostSupportsInteractiveSession private Guid instanceId = Guid.NewGuid(); private EditorServicesPSHostUserInterface hostUserInterface; private IHostSupportsInteractiveSession hostSupportsInteractiveSession; - private PowerShellContext powerShellContext; + private PowerShellContextService powerShellContext; #endregion @@ -47,7 +47,7 @@ public class EditorServicesPSHost : PSHost, IHostSupportsInteractiveSession /// /// An ILogger implementation to use for this host. public EditorServicesPSHost( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, HostDetails hostDetails, EditorServicesPSHostUserInterface hostUserInterface, ILogger logger) @@ -269,7 +269,7 @@ public override void ExitNestedPrompt() /// public override void NotifyBeginApplication() { - Logger.Write(LogLevel.Verbose, "NotifyBeginApplication() called."); + Logger.LogTrace("NotifyBeginApplication() called."); this.hostUserInterface.IsNativeApplicationRunning = true; } @@ -278,7 +278,7 @@ public override void NotifyBeginApplication() /// public override void NotifyEndApplication() { - Logger.Write(LogLevel.Verbose, "NotifyEndApplication() called."); + Logger.LogTrace("NotifyEndApplication() called."); this.hostUserInterface.IsNativeApplicationRunning = false; } diff --git a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs similarity index 98% rename from src/PowerShellEditorServices/Session/Host/EditorServicesPSHostUserInterface.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs index 24ea1c8db..685f812a8 100644 --- a/src/PowerShellEditorServices/Session/Host/EditorServicesPSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs @@ -12,13 +12,12 @@ 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides an implementation of the PSHostUserInterface class @@ -42,7 +41,7 @@ public abstract class EditorServicesPSHostUserInterface : /// /// The PowerShellContext to use for executing commands. /// - protected PowerShellContext powerShellContext; + protected PowerShellContextService powerShellContext; #endregion @@ -103,7 +102,7 @@ public abstract class EditorServicesPSHostUserInterface : /// The PSHostRawUserInterface implementation to use for this host. /// An ILogger implementation to use for this host. public EditorServicesPSHostUserInterface( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, PSHostRawUserInterface rawUserInterface, ILogger logger) { @@ -159,7 +158,7 @@ private void ShowCommandPrompt() } else { - Logger.Write(LogLevel.Verbose, "StartReadLoop called while read loop is already running"); + Logger.LogTrace("StartReadLoop called while read loop is already running"); } } @@ -846,7 +845,7 @@ private async Task StartReplLoopAsync(CancellationToken cancellationToken) true, OutputType.Error); - Logger.WriteException("Caught exception while reading command line", e); + Logger.LogException("Caught exception while reading command line", e); } finally { @@ -877,8 +876,7 @@ private InputPromptHandler CreateInputPromptHandler() { if (this.activePromptHandler != null) { - Logger.Write( - LogLevel.Error, + Logger.LogError( "Prompt handler requested while another prompt is already active."); } @@ -893,8 +891,7 @@ private ChoicePromptHandler CreateChoicePromptHandler() { if (this.activePromptHandler != null) { - Logger.Write( - LogLevel.Error, + Logger.LogError( "Prompt handler requested while another prompt is already active."); } diff --git a/src/PowerShellEditorServices/Session/Host/IHostInput.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs similarity index 90% rename from src/PowerShellEditorServices/Session/Host/IHostInput.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs index 28c79839d..cdce37129 100644 --- a/src/PowerShellEditorServices/Session/Host/IHostInput.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides methods for integrating with the host's input system. @@ -25,4 +25,4 @@ public interface IHostInput /// void SendControlC(); } -} \ No newline at end of file +} diff --git a/src/PowerShellEditorServices/Session/Host/IHostOutput.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs similarity index 98% rename from src/PowerShellEditorServices/Session/Host/IHostOutput.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs index 4f36bc54f..9b5565f37 100644 --- a/src/PowerShellEditorServices/Session/Host/IHostOutput.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs @@ -5,7 +5,7 @@ using System; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a simplified interface for writing output to a diff --git a/src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptEvents.cs similarity index 63% rename from src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptEvents.cs index 3a04eaf1e..967830d9d 100644 --- a/src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptEvents.cs @@ -3,16 +3,10 @@ // 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 +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { public class ShowChoicePromptRequest { - public static readonly - RequestType Type = - RequestType.Create("powerShell/showChoicePrompt"); - public bool IsMultiChoice { get; set; } public string Caption { get; set; } @@ -33,10 +27,6 @@ public class ShowChoicePromptResponse public class ShowInputPromptRequest { - public static readonly - RequestType Type = - RequestType.Create("powerShell/showInputPrompt"); - /// /// Gets or sets the name of the field. /// diff --git a/src/PowerShellEditorServices.Host/PSHost/PromptHandlers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs similarity index 66% rename from src/PowerShellEditorServices.Host/PSHost/PromptHandlers.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs index d8ec88c4e..98d4905b5 100644 --- a/src/PowerShellEditorServices.Host/PSHost/PromptHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs @@ -4,57 +4,54 @@ // 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; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; -namespace Microsoft.PowerShell.EditorServices.Host +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class ProtocolChoicePromptHandler : ConsoleChoicePromptHandler { - private IHostInput hostInput; - private IMessageSender messageSender; - private TaskCompletionSource readLineTask; + private readonly ILanguageServer _languageServer; + private readonly IHostInput _hostInput; + private TaskCompletionSource _readLineTask; public ProtocolChoicePromptHandler( - IMessageSender messageSender, + ILanguageServer languageServer, IHostInput hostInput, IHostOutput hostOutput, ILogger logger) : base(hostOutput, logger) { - this.hostInput = hostInput; + _languageServer = languageServer; + 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) + _languageServer.SendRequest( + "powerShell/showChoicePrompt", + new ShowChoicePromptRequest + { + IsMultiChoice = this.IsMultiChoice, + Caption = this.Caption, + Message = this.Message, + Choices = this.Choices, + DefaultChoices = this.DefaultChoices + }) .ContinueWith(HandlePromptResponse) .ConfigureAwait(false); } protected override Task ReadInputStringAsync(CancellationToken cancellationToken) { - this.readLineTask = new TaskCompletionSource(); - return this.readLineTask.Task; + this._readLineTask = new TaskCompletionSource(); + return this._readLineTask.Task; } private void HandlePromptResponse( @@ -70,12 +67,12 @@ private void HandlePromptResponse( response.ResponseText, OutputType.Normal); - this.readLineTask.TrySetResult(response.ResponseText); + this._readLineTask.TrySetResult(response.ResponseText); } else { // Cancel the current prompt - this.hostInput.SendControlC(); + this._hostInput.SendControlC(); } } else @@ -83,51 +80,48 @@ private void HandlePromptResponse( if (responseTask.IsFaulted) { // Log the error - Logger.Write( - LogLevel.Error, + Logger.LogError( "ShowChoicePrompt request failed with error:\r\n{0}", responseTask.Exception.ToString()); } // Cancel the current prompt - this.hostInput.SendControlC(); + this._hostInput.SendControlC(); } - this.readLineTask = null; + this._readLineTask = null; } } internal class ProtocolInputPromptHandler : ConsoleInputPromptHandler { - private IHostInput hostInput; - private IMessageSender messageSender; + private readonly ILanguageServer _languageServer; + private readonly IHostInput hostInput; private TaskCompletionSource readLineTask; public ProtocolInputPromptHandler( - IMessageSender messageSender, + ILanguageServer languageServer, IHostInput hostInput, IHostOutput hostOutput, ILogger logger) : base(hostOutput, logger) { + _languageServer = languageServer; 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) + _languageServer.SendRequest( + "powerShell/showInputPrompt", + new ShowInputPromptRequest + { + Name = fieldDetails.Name, + Label = fieldDetails.Label + }).ContinueWith(HandlePromptResponse) .ConfigureAwait(false); } @@ -163,8 +157,7 @@ private void HandlePromptResponse( if (responseTask.IsFaulted) { // Log the error - Logger.Write( - LogLevel.Error, + Logger.LogError( "ShowInputPrompt request failed with error:\r\n{0}", responseTask.Exception.ToString()); } diff --git a/src/PowerShellEditorServices.Host/PSHost/ProtocolPSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs similarity index 65% rename from src/PowerShellEditorServices.Host/PSHost/ProtocolPSHostUserInterface.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs index 76b1f7252..6053245c2 100644 --- a/src/PowerShellEditorServices.Host/PSHost/ProtocolPSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs @@ -3,23 +3,19 @@ // 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 Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Host +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class ProtocolPSHostUserInterface : EditorServicesPSHostUserInterface { #region Private Fields - private IMessageSender messageSender; - private OutputDebouncer outputDebouncer; + private readonly ILanguageServer _languageServer; #endregion @@ -31,25 +27,12 @@ internal class ProtocolPSHostUserInterface : EditorServicesPSHostUserInterface /// /// public ProtocolPSHostUserInterface( - PowerShellContext powerShellContext, - IMessageSender messageSender, + ILanguageServer languageServer, + PowerShellContextService powerShellContext, 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; - } + _languageServer = languageServer; } #endregion @@ -81,14 +64,7 @@ public override void WriteOutput( ConsoleColor foregroundColor, ConsoleColor backgroundColor) { - // TODO: This should use a synchronous method! - this.outputDebouncer.InvokeAsync( - new OutputWrittenEventArgs( - outputString, - includeNewLine, - outputType, - foregroundColor, - backgroundColor)).Wait(); + // TODO: Invoke the "output" notification! } /// @@ -100,6 +76,7 @@ protected override void UpdateProgress( long sourceId, ProgressDetails progressDetails) { + // TODO: Send a new message. } protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) @@ -112,12 +89,12 @@ protected override Task ReadCommandLineAsync(CancellationToken cancellat protected override InputPromptHandler OnCreateInputPromptHandler() { - return new ProtocolInputPromptHandler(this.messageSender, this, this, this.Logger); + return new ProtocolInputPromptHandler(_languageServer, this, this, this.Logger); } protected override ChoicePromptHandler OnCreateChoicePromptHandler() { - return new ProtocolChoicePromptHandler(this.messageSender, this, this, this.Logger); + return new ProtocolChoicePromptHandler(_languageServer, this, this, this.Logger); } } } diff --git a/src/PowerShellEditorServices/Session/Host/SimplePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs similarity index 94% rename from src/PowerShellEditorServices/Session/Host/SimplePSHostRawUserInterface.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs index 89895a776..701f48851 100644 --- a/src/PowerShellEditorServices/Session/Host/SimplePSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs @@ -3,11 +3,11 @@ // 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; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides an simple implementation of the PSHostRawUserInterface class. @@ -152,8 +152,7 @@ public override Size MaxWindowSize /// A KeyInfo struct with details about the current keypress. public override KeyInfo ReadKey(ReadKeyOptions options) { - Logger.Write( - LogLevel.Warning, + Logger.LogWarning( "PSHostRawUserInterface.ReadKey was called"); throw new System.NotImplementedException(); @@ -164,8 +163,7 @@ public override KeyInfo ReadKey(ReadKeyOptions options) /// public override void FlushInputBuffer() { - Logger.Write( - LogLevel.Warning, + Logger.LogWarning( "PSHostRawUserInterface.FlushInputBuffer was called"); } @@ -192,8 +190,7 @@ public override void ScrollBufferContents( Rectangle clip, BufferCell fill) { - Logger.Write( - LogLevel.Warning, + Logger.LogWarning( "PSHostRawUserInterface.ScrollBufferContents was called"); } @@ -206,8 +203,7 @@ public override void SetBufferContents( Rectangle rectangle, BufferCell fill) { - Logger.Write( - LogLevel.Warning, + Logger.LogWarning( "PSHostRawUserInterface.SetBufferContents was called"); } @@ -220,8 +216,7 @@ public override void SetBufferContents( Coordinates origin, BufferCell[,] contents) { - Logger.Write( - LogLevel.Warning, + Logger.LogWarning( "PSHostRawUserInterface.SetBufferContents was called"); } diff --git a/src/PowerShellEditorServices/Session/Host/TerminalPSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs similarity index 98% rename from src/PowerShellEditorServices/Session/Host/TerminalPSHostRawUserInterface.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs index be217c9d9..c1a45fcb1 100644 --- a/src/PowerShellEditorServices/Session/Host/TerminalPSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs @@ -3,14 +3,13 @@ // 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 Microsoft.Extensions.Logging; using System; using System.Management.Automation; using System.Management.Automation.Host; using System.Threading; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides an implementation of the PSHostRawUserInterface class @@ -204,8 +203,7 @@ public override KeyInfo ReadKey(ReadKeyOptions options) /// public override void FlushInputBuffer() { - Logger.Write( - LogLevel.Warning, + Logger.LogWarning( "PSHostRawUserInterface.FlushInputBuffer was called"); } diff --git a/src/PowerShellEditorServices/Session/Host/TerminalPSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs similarity index 95% rename from src/PowerShellEditorServices/Session/Host/TerminalPSHostUserInterface.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs index 82dab2d1e..93e8173ad 100644 --- a/src/PowerShellEditorServices/Session/Host/TerminalPSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs @@ -3,16 +3,15 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Console; +using System; +using System.Management.Automation.Host; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { - 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 @@ -37,7 +36,7 @@ public class TerminalPSHostUserInterface : EditorServicesPSHostUserInterface /// An ILogger implementation to use for this host. /// The InternalHost instance from the origin runspace. public TerminalPSHostUserInterface( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, ILogger logger, PSHost internalHost) : base( diff --git a/src/PowerShellEditorServices/Session/IPromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs similarity index 97% rename from src/PowerShellEditorServices/Session/IPromptContext.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs index 157715e7d..2d1785670 100644 --- a/src/PowerShellEditorServices/Session/IPromptContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides methods for interacting with implementations of ReadLine. diff --git a/src/PowerShellEditorServices/Session/IRunspaceCapability.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs similarity index 79% rename from src/PowerShellEditorServices/Session/IRunspaceCapability.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs index 38d14fb96..466d80e3b 100644 --- a/src/PowerShellEditorServices/Session/IRunspaceCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal interface IRunspaceCapability { diff --git a/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs similarity index 79% rename from src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs index 55540ba9d..2b9fb1f6b 100644 --- a/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs @@ -8,7 +8,7 @@ using System.Management.Automation.Host; using System.Management.Automation.Runspaces; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal interface IVersionSpecificOperations { @@ -17,13 +17,13 @@ internal interface IVersionSpecificOperations void PauseDebugger(Runspace runspace); IEnumerable ExecuteCommandInDebugger( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, Runspace currentRunspace, PSCommand psCommand, bool sendOutputToHost, out DebuggerResumeAction? debuggerResumeAction); - void StopCommandInDebugger(PowerShellContext powerShellContext); + void StopCommandInDebugger(PowerShellContextService powerShellContext); bool IsDebuggerStopped(PromptNest promptNest, Runspace runspace); diff --git a/src/PowerShellEditorServices/Session/InvocationEventQueue.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs similarity index 94% rename from src/PowerShellEditorServices/Session/InvocationEventQueue.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs index b6e9a9c0b..b28f0e2d2 100644 --- a/src/PowerShellEditorServices/Session/InvocationEventQueue.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs @@ -12,7 +12,7 @@ using System.Threading; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { using System.Management.Automation; @@ -33,20 +33,20 @@ internal class InvocationEventQueue private readonly Runspace _runspace; - private readonly PowerShellContext _powerShellContext; + private readonly PowerShellContextService _powerShellContext; private InvocationRequest _invocationRequest; private SemaphoreSlim _lock = AsyncUtils.CreateSimpleLockingSemaphore(); - private InvocationEventQueue(PowerShellContext powerShellContext, PromptNest promptNest) + private InvocationEventQueue(PowerShellContextService powerShellContext, PromptNest promptNest) { _promptNest = promptNest; _powerShellContext = powerShellContext; _runspace = powerShellContext.CurrentRunspace.Runspace; } - internal static InvocationEventQueue Create(PowerShellContext powerShellContext, PromptNest promptNest) + internal static InvocationEventQueue Create(PowerShellContextService powerShellContext, PromptNest promptNest) { var eventQueue = new InvocationEventQueue(powerShellContext, promptNest); eventQueue.CreateInvocationSubscriber(); @@ -57,11 +57,11 @@ internal static InvocationEventQueue Create(PowerShellContext powerShellContext, /// 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. + /// to utilize. /// /// /// Avoid using this method directly if possible. - /// will route commands + /// will route commands /// through this method if required. /// /// The expected result type. @@ -184,7 +184,7 @@ private void OnPowerShellIdle(object sender, EventArgs e) return; } - currentRequest = _invocationRequest; + currentRequest = _invocationRequest; } finally { diff --git a/src/PowerShellEditorServices/Session/LegacyReadLineContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs similarity index 89% rename from src/PowerShellEditorServices/Session/LegacyReadLineContext.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs index ad68d0512..8f116a201 100644 --- a/src/PowerShellEditorServices/Session/LegacyReadLineContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs @@ -5,15 +5,14 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Console; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class LegacyReadLineContext : IPromptContext { private readonly ConsoleReadLine _legacyReadLine; - internal LegacyReadLineContext(PowerShellContext powerShellContext) + internal LegacyReadLineContext(PowerShellContextService powerShellContext) { _legacyReadLine = new ConsoleReadLine(powerShellContext); } diff --git a/src/PowerShellEditorServices/Session/OutputType.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs similarity index 92% rename from src/PowerShellEditorServices/Session/OutputType.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs index ad67f6891..92b5ef540 100644 --- a/src/PowerShellEditorServices/Session/OutputType.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Enumerates the types of output lines that will be sent @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.EditorServices public enum OutputType { /// - /// A normal output line, usually written with the or Write-Host or + /// A normal output line, usually written with the or Write-Host or /// Write-Output cmdlets. /// Normal, diff --git a/src/PowerShellEditorServices/Session/OutputWrittenEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs similarity index 89% rename from src/PowerShellEditorServices/Session/OutputWrittenEventArgs.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs index b1408a991..284dc5dc5 100644 --- a/src/PowerShellEditorServices/Session/OutputWrittenEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs @@ -5,7 +5,7 @@ using System; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides details about output that has been written to the @@ -24,7 +24,7 @@ public class OutputWrittenEventArgs public OutputType OutputType { get; private set; } /// - /// Gets a boolean which indicates whether a newline + /// Gets a boolean which indicates whether a newline /// should be written after the output. /// public bool IncludeNewLine { get; private set; } @@ -49,9 +49,9 @@ public class OutputWrittenEventArgs /// The background color of the output text. public OutputWrittenEventArgs( string outputText, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, + bool includeNewLine, + OutputType outputType, + ConsoleColor foregroundColor, ConsoleColor backgroundColor) { this.OutputText = outputText; @@ -62,4 +62,3 @@ public OutputWrittenEventArgs( } } } - diff --git a/src/PowerShellEditorServices/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs similarity index 95% rename from src/PowerShellEditorServices/Session/PSReadLinePromptContext.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs index a71e32ed9..26b3cc5a7 100644 --- a/src/PowerShellEditorServices/Session/PSReadLinePromptContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs @@ -9,13 +9,14 @@ 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 { +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +{ using System.Management.Automation; + using Microsoft.Extensions.Logging; - internal class PSReadLinePromptContext : IPromptContext { + internal class PSReadLinePromptContext : IPromptContext + { private const string ReadLineScript = @" [System.Diagnostics.DebuggerHidden()] [System.Diagnostics.DebuggerStepThrough()] @@ -43,7 +44,7 @@ internal class PSReadLinePromptContext : IPromptContext { return [Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null] }"; - private readonly PowerShellContext _powerShellContext; + private readonly PowerShellContextService _powerShellContext; private PromptNest _promptNest; @@ -56,7 +57,7 @@ internal class PSReadLinePromptContext : IPromptContext { private PSReadLineProxy _readLineProxy; internal PSReadLinePromptContext( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, PromptNest promptNest, InvocationEventQueue invocationEventQueue, PSReadLineProxy readLineProxy) diff --git a/src/PowerShellEditorServices/Session/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs similarity index 96% rename from src/PowerShellEditorServices/Session/PSReadLineProxy.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs index 50aaf4af3..c15f4c719 100644 --- a/src/PowerShellEditorServices/Session/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs @@ -5,9 +5,9 @@ using System; using System.Reflection; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class PSReadLineProxy { @@ -109,8 +109,7 @@ private static InvalidOperationException NewInvalidPSReadLineVersionException( string memberName, ILogger logger) { - logger.Write( - LogLevel.Error, + logger.LogError( $"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/Services/PowerShellContext/Session/PipelineExecutionRequest.cs similarity index 90% rename from src/PowerShellEditorServices/Session/PipelineExecutionRequest.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PipelineExecutionRequest.cs index 1c69f6a15..21af0280f 100644 --- a/src/PowerShellEditorServices/Session/PipelineExecutionRequest.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PipelineExecutionRequest.cs @@ -8,7 +8,7 @@ using System.Text; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal interface IPipelineExecutionRequest { @@ -24,7 +24,7 @@ internal interface IPipelineExecutionRequest /// The expected result type of the execution. internal class PipelineExecutionRequest : IPipelineExecutionRequest { - private PowerShellContext _powerShellContext; + private PowerShellContextService _powerShellContext; private PSCommand _psCommand; private StringBuilder _errorMessages; private ExecutionOptions _executionOptions; @@ -38,7 +38,7 @@ public Task> Results public Task WaitTask { get { return Results; } } public PipelineExecutionRequest( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, PSCommand psCommand, StringBuilder errorMessages, bool sendOutputToHost) @@ -54,7 +54,7 @@ public PipelineExecutionRequest( public PipelineExecutionRequest( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, PSCommand psCommand, StringBuilder errorMessages, ExecutionOptions executionOptions) diff --git a/src/PowerShellEditorServices/Session/PowerShell5Operations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs similarity index 93% rename from src/PowerShellEditorServices/Session/PowerShell5Operations.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs index 20954ac21..2e7fedcf8 100644 --- a/src/PowerShellEditorServices/Session/PowerShell5Operations.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs @@ -9,7 +9,7 @@ using System.Management.Automation.Host; using System.Management.Automation.Runspaces; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { internal class PowerShell5Operations : IVersionSpecificOperations { @@ -35,7 +35,7 @@ public virtual bool IsDebuggerStopped(PromptNest promptNest, Runspace runspace) } public IEnumerable ExecuteCommandInDebugger( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, Runspace currentRunspace, PSCommand psCommand, bool sendOutputToHost, @@ -83,7 +83,7 @@ public IEnumerable ExecuteCommandInDebugger( return results; } - public void StopCommandInDebugger(PowerShellContext powerShellContext) + public void StopCommandInDebugger(PowerShellContextService 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) @@ -104,4 +104,3 @@ public void ExitNestedPrompt(PSHost host) } } } - diff --git a/src/PowerShellEditorServices/Session/PowerShellContextState.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs similarity index 94% rename from src/PowerShellEditorServices/Session/PowerShellContextState.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs index 2075c04d5..c2742e270 100644 --- a/src/PowerShellEditorServices/Session/PowerShellContextState.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Enumerates the possible states for a PowerShellContext. @@ -14,9 +14,9 @@ public enum PowerShellContextState /// Indicates an unknown, potentially uninitialized state. /// Unknown = 0, - + /// - /// Indicates the state where the session is starting but + /// Indicates the state where the session is starting but /// not yet fully initialized. /// NotStarted, @@ -26,7 +26,7 @@ public enum PowerShellContextState /// for execution. /// Ready, - + /// /// Indicates that the session is currently running a command. /// @@ -44,4 +44,3 @@ public enum PowerShellContextState Disposed } } - diff --git a/src/PowerShellEditorServices/Session/PowerShellExecutionResult.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs similarity index 93% rename from src/PowerShellEditorServices/Session/PowerShellExecutionResult.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs index ee15ae97a..1be771187 100644 --- a/src/PowerShellEditorServices/Session/PowerShellExecutionResult.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Enumerates the possible execution results that can occur after @@ -37,4 +37,3 @@ public enum PowerShellExecutionResult Completed } } - diff --git a/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs similarity index 93% rename from src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs index efd5e5feb..6e1a1a32e 100644 --- a/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs @@ -3,12 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.Extensions.Logging; using System; using System.Collections; using System.Management.Automation.Runspaces; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Defines the possible enumeration values for the PowerShell process architecture. @@ -101,7 +101,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog try { - var psVersionTable = PowerShellContext.ExecuteScriptAndGetItem("$PSVersionTable", runspace); + var psVersionTable = PowerShellContextService.ExecuteScriptAndGetItem("$PSVersionTable", runspace); if (psVersionTable != null) { var edition = psVersionTable["PSEdition"] as string; @@ -134,7 +134,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog versionString = powerShellVersion.ToString(); } - var arch = PowerShellContext.ExecuteScriptAndGetItem("$env:PROCESSOR_ARCHITECTURE", runspace); + var arch = PowerShellContextService.ExecuteScriptAndGetItem("$env:PROCESSOR_ARCHITECTURE", runspace); if (arch != null) { if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase)) @@ -150,8 +150,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog } catch (Exception ex) { - logger.Write( - LogLevel.Warning, + logger.LogWarning( "Failed to look up PowerShell version, defaulting to version 5.\r\n\r\n" + ex.ToString()); } diff --git a/src/PowerShellEditorServices/Session/ProgressDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs similarity index 92% rename from src/PowerShellEditorServices/Session/ProgressDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs index b88d7c1ae..fc7fb2047 100644 --- a/src/PowerShellEditorServices/Session/ProgressDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs @@ -5,7 +5,7 @@ using System.Management.Automation; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides details about the progress of a particular activity. @@ -30,4 +30,3 @@ internal static ProgressDetails Create(ProgressRecord progressRecord) } } } - diff --git a/src/PowerShellEditorServices/Session/PromptNest.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs similarity index 99% rename from src/PowerShellEditorServices/Session/PromptNest.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs index 9cf4437f2..380c4bb81 100644 --- a/src/PowerShellEditorServices/Session/PromptNest.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { using System; using System.Management.Automation; @@ -24,7 +24,7 @@ internal class PromptNest : IDisposable private IHostInput _consoleReader; - private PowerShellContext _powerShellContext; + private PowerShellContextService _powerShellContext; private IVersionSpecificOperations _versionSpecificOperations; @@ -55,7 +55,7 @@ internal class PromptNest : IDisposable /// is set to the initial runspace. /// internal PromptNest( - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, PowerShell initialPowerShell, IHostInput consoleReader, IVersionSpecificOperations versionSpecificOperations) diff --git a/src/PowerShellEditorServices/Session/PromptNestFrame.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs similarity index 98% rename from src/PowerShellEditorServices/Session/PromptNestFrame.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs index cae7dfb8a..2d6a1a473 100644 --- a/src/PowerShellEditorServices/Session/PromptNestFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { using System.Management.Automation; diff --git a/src/PowerShellEditorServices/Session/PromptNestFrameType.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs similarity index 81% rename from src/PowerShellEditorServices/Session/PromptNestFrameType.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs index b42b42098..ec4d13977 100644 --- a/src/PowerShellEditorServices/Session/PromptNestFrameType.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs @@ -5,7 +5,7 @@ using System; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { [Flags] internal enum PromptNestFrameType diff --git a/src/PowerShellEditorServices/Session/RunspaceChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs similarity index 96% rename from src/PowerShellEditorServices/Session/RunspaceChangedEventArgs.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs index 12dc76ade..77512c9c1 100644 --- a/src/PowerShellEditorServices/Session/RunspaceChangedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs @@ -5,7 +5,7 @@ using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Defines the set of actions that will cause the runspace to be changed. diff --git a/src/PowerShellEditorServices/Session/RunspaceDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Session/RunspaceDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs index d016e2fe8..54f38cf34 100644 --- a/src/PowerShellEditorServices/Session/RunspaceDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs @@ -4,12 +4,13 @@ // using Microsoft.CSharp.RuntimeBinder; -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Management.Automation.Runspaces; using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Specifies the possible types of a runspace. @@ -219,7 +220,7 @@ internal static RunspaceDetails CreateFromRunspace( // Grab the $host.name which will tell us if we're in a PSRP session or not string hostName = - PowerShellContext.ExecuteScriptAndGetItem( + PowerShellContextService.ExecuteScriptAndGetItem( "$Host.Name", runspace, defaultValue: string.Empty); diff --git a/src/PowerShellEditorServices/Session/RunspaceHandle.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs similarity index 84% rename from src/PowerShellEditorServices/Session/RunspaceHandle.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs index 4947eadbe..650cce9d2 100644 --- a/src/PowerShellEditorServices/Session/RunspaceHandle.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs @@ -7,7 +7,7 @@ using System.Management.Automation.Host; using System.Management.Automation.Runspaces; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides a handle to the runspace that is managed by @@ -15,7 +15,7 @@ namespace Microsoft.PowerShell.EditorServices /// public class RunspaceHandle : IDisposable { - private PowerShellContext powerShellContext; + private PowerShellContextService powerShellContext; /// /// Gets the runspace that is held by this handle. @@ -35,11 +35,11 @@ public Runspace Runspace /// given runspace. /// /// The PowerShellContext instance which manages the runspace. - public RunspaceHandle(PowerShellContext powerShellContext) + public RunspaceHandle(PowerShellContextService powerShellContext) : this(powerShellContext, false) { } - internal RunspaceHandle(PowerShellContext powerShellContext, bool isReadLine) + internal RunspaceHandle(PowerShellContextService powerShellContext, bool isReadLine) { this.powerShellContext = powerShellContext; this.IsReadLine = isReadLine; diff --git a/src/PowerShellEditorServices/Session/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs similarity index 96% rename from src/PowerShellEditorServices/Session/SessionDetails.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs index 708e6ec9d..d30722d9f 100644 --- a/src/PowerShellEditorServices/Session/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs @@ -3,12 +3,12 @@ // 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; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides details about the current PowerShell session. diff --git a/src/PowerShellEditorServices/Session/SessionStateChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs similarity index 95% rename from src/PowerShellEditorServices/Session/SessionStateChangedEventArgs.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs index 9a0fed0f3..70c40a7f2 100644 --- a/src/PowerShellEditorServices/Session/SessionStateChangedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs @@ -5,7 +5,7 @@ using System; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides details about a change in state of a PowerShellContext. @@ -45,4 +45,3 @@ public SessionStateChangedEventArgs( } } } - diff --git a/src/PowerShellEditorServices/Session/ThreadController.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs similarity index 98% rename from src/PowerShellEditorServices/Session/ThreadController.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs index 8720e3fa7..4066d5117 100644 --- a/src/PowerShellEditorServices/Session/ThreadController.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Session +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides the ability to route PowerShell command invocations to a specific thread. diff --git a/src/PowerShellEditorServices/Templates/TemplateService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs similarity index 85% rename from src/PowerShellEditorServices/Templates/TemplateService.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs index 18f58085c..ed0846481 100644 --- a/src/PowerShellEditorServices/Templates/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs @@ -3,13 +3,16 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Linq; using System.Management.Automation; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Templates +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Provides a service for listing PowerShell project templates and creating @@ -20,10 +23,10 @@ public class TemplateService { #region Private Fields - private ILogger logger; + private readonly ILogger logger; private bool isPlasterLoaded; private bool? isPlasterInstalled; - private PowerShellContext powerShellContext; + private readonly PowerShellContextService powerShellContext; #endregion @@ -33,12 +36,12 @@ public class TemplateService /// 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) + /// An ILoggerFactory implementation used for writing log messages. + public TemplateService(PowerShellContextService powerShellContext, ILoggerFactory factory) { Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - this.logger = logger; + this.logger = factory.CreateLogger(); this.powerShellContext = powerShellContext; } @@ -70,7 +73,7 @@ public async Task ImportPlasterIfInstalledAsync() .AddCommand("Select-Object") .AddParameter("First", 1); - this.logger.Write(LogLevel.Verbose, "Checking if Plaster is installed..."); + this.logger.LogTrace("Checking if Plaster is installed..."); var getResult = await this.powerShellContext.ExecuteCommandAsync( @@ -82,14 +85,12 @@ await this.powerShellContext.ExecuteCommandAsync( this.isPlasterInstalled.Value ? string.Empty : "not "; - this.logger.Write( - LogLevel.Verbose, - $"Plaster is {installedQualifier}installed!"); + this.logger.LogTrace($"Plaster is {installedQualifier}installed!"); // Attempt to load plaster if (this.isPlasterInstalled.Value && this.isPlasterLoaded == false) { - this.logger.Write(LogLevel.Verbose, "Loading Plaster..."); + this.logger.LogTrace("Loading Plaster..."); psCommand = new PSCommand(); psCommand @@ -106,9 +107,7 @@ await this.powerShellContext.ExecuteCommandAsync( this.isPlasterInstalled.Value ? "was" : "could not be"; - this.logger.Write( - LogLevel.Verbose, - $"Plaster {loadedQualifier} loaded successfully!"); + this.logger.LogTrace($"Plaster {loadedQualifier} loaded successfully!"); } } @@ -130,7 +129,7 @@ public async Task GetAvailableTemplatesAsync( if (!this.isPlasterLoaded) { throw new InvalidOperationException("Plaster is not loaded, templates cannot be accessed."); - }; + } PSCommand psCommand = new PSCommand(); psCommand.AddCommand("Get-PlasterTemplate"); @@ -144,9 +143,7 @@ public async Task GetAvailableTemplatesAsync( await this.powerShellContext.ExecuteCommandAsync( psCommand, false, false); - this.logger.Write( - LogLevel.Verbose, - $"Found {templateObjects.Count()} Plaster templates"); + this.logger.LogTrace($"Found {templateObjects.Count()} Plaster templates"); return templateObjects @@ -166,8 +163,7 @@ public async Task CreateFromTemplateAsync( string templatePath, string destinationPath) { - this.logger.Write( - LogLevel.Verbose, + this.logger.LogTrace( $"Invoking Plaster...\n\n TemplatePath: {templatePath}\n DestinationPath: {destinationPath}"); PSCommand command = new PSCommand(); @@ -196,8 +192,6 @@ await this.powerShellContext.ExecuteCommandAsync( private static TemplateDetails CreateTemplateDetails(PSObject psObject) { - object[] tags = psObject.Members["Tags"].Value as object[]; - return new TemplateDetails { Title = psObject.Members["Title"].Value as string, @@ -206,7 +200,7 @@ private static TemplateDetails CreateTemplateDetails(PSObject psObject) Description = psObject.Members["Description"].Value as string, TemplatePath = psObject.Members["TemplatePath"].Value as string, Tags = - tags != null + psObject.Members["Tags"].Value is object[] tags ? string.Join(", ", tags) : string.Empty }; diff --git a/src/PowerShellEditorServices/Language/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs similarity index 75% rename from src/PowerShellEditorServices/Language/CommandHelpers.cs rename to src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs index 9c72e0eef..ced8732f7 100644 --- a/src/PowerShellEditorServices/Language/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs @@ -1,34 +1,36 @@ -// +// // 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.Collections.Concurrent; using System.Linq; using System.Management.Automation; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { /// /// Provides utility methods for working with PowerShell commands. /// - public class CommandHelpers + public static class CommandHelpers { - private static HashSet NounBlackList = - new HashSet - { - "Module", - "Script", - "Package", - "PackageProvider", - "PackageSource", - "InstalledModule", - "InstalledScript", - "ScriptFileInfo", - "PSRepository" - }; + private static readonly ConcurrentDictionary NounExclusionList = + new ConcurrentDictionary(); + + static CommandHelpers() + { + NounExclusionList.TryAdd("Module", true); + NounExclusionList.TryAdd("Script", true); + NounExclusionList.TryAdd("Package", true); + NounExclusionList.TryAdd("PackageProvider", true); + NounExclusionList.TryAdd("PackageSource", true); + NounExclusionList.TryAdd("InstalledModule", true); + NounExclusionList.TryAdd("InstalledScript", true); + NounExclusionList.TryAdd("ScriptFileInfo", true); + NounExclusionList.TryAdd("PSRepository", true); + } /// /// Gets the CommandInfo instance for a command with a particular name. @@ -38,7 +40,7 @@ public class CommandHelpers /// A CommandInfo object with details about the specified command. public static async Task GetCommandInfoAsync( string commandName, - PowerShellContext powerShellContext) + PowerShellContextService powerShellContext) { Validate.IsNotNull(nameof(commandName), commandName); @@ -47,7 +49,7 @@ public static async Task GetCommandInfoAsync( // load PackageManagement or PowerShellGet because they cause // a major slowdown in IntelliSense. var commandParts = commandName.Split('-'); - if (commandParts.Length == 2 && NounBlackList.Contains(commandParts[1])) + if (commandParts.Length == 2 && NounExclusionList.ContainsKey(commandParts[1])) { return null; } @@ -73,12 +75,10 @@ public static async Task GetCommandInfoAsync( /// public static async Task GetCommandSynopsisAsync( CommandInfo commandInfo, - PowerShellContext powerShellContext) + PowerShellContextService powerShellContext) { string synopsisString = string.Empty; - PSObject helpObject = null; - if (commandInfo != null && (commandInfo.CommandType == CommandTypes.Cmdlet || commandInfo.CommandType == CommandTypes.Function || @@ -90,7 +90,7 @@ public static async Task GetCommandSynopsisAsync( command.AddParameter("ErrorAction", "Ignore"); var results = await powerShellContext.ExecuteCommandAsync(command, false, false); - helpObject = results.FirstOrDefault(); + PSObject helpObject = results.FirstOrDefault(); if (helpObject != null) { @@ -111,4 +111,3 @@ public static async Task GetCommandSynopsisAsync( } } } - diff --git a/src/PowerShellEditorServices/Symbols/IDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs similarity index 80% rename from src/PowerShellEditorServices/Symbols/IDocumentSymbolProvider.cs rename to src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs index 02638c5b0..efb5af5d8 100644 --- a/src/PowerShellEditorServices/Symbols/IDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs @@ -4,13 +4,14 @@ // using System.Collections.Generic; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices.Symbols +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// Specifies the contract for a document symbols provider. /// - public interface IDocumentSymbolProvider : IFeatureProvider + public interface IDocumentSymbolProvider { /// /// Provides a list of symbols for the given document. diff --git a/src/PowerShellEditorServices/Symbols/IDocumentSymbols.cs b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbols.cs similarity index 80% rename from src/PowerShellEditorServices/Symbols/IDocumentSymbols.cs rename to src/PowerShellEditorServices/Services/Symbols/IDocumentSymbols.cs index 6c1937627..b1cd09c1b 100644 --- a/src/PowerShellEditorServices/Symbols/IDocumentSymbols.cs +++ b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbols.cs @@ -4,8 +4,10 @@ // using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices.Symbols +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// Specifies the contract for an implementation of @@ -17,7 +19,7 @@ public interface IDocumentSymbols /// Gets the collection of IDocumentSymbolsProvider implementations /// that are registered with this component. /// - IFeatureProviderCollection Providers { get; } + Collection Providers { get; } /// /// Provides a list of symbols for the given document. diff --git a/src/PowerShellEditorServices/Language/ParameterSetSignatures.cs b/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs similarity index 80% rename from src/PowerShellEditorServices/Language/ParameterSetSignatures.cs rename to src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs index 68cd8279d..df61a0179 100644 --- a/src/PowerShellEditorServices/Language/ParameterSetSignatures.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs @@ -3,10 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System.Collections.Concurrent; using System.Collections.Generic; using System.Management.Automation; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// A class for containing the commandName, the command's @@ -15,6 +17,7 @@ namespace Microsoft.PowerShell.EditorServices public class ParameterSetSignatures { #region Properties + /// /// Gets the name of the command /// @@ -29,8 +32,9 @@ public class ParameterSetSignatures /// Gets the script extent of the command /// public ScriptRegion ScriptRegion { get; internal set; } + #endregion - + /// /// Constructs an instance of a ParameterSetSignatures object /// @@ -54,21 +58,23 @@ public ParameterSetSignatures(IEnumerable commandInfoSe /// public class ParameterSetSignature { - private static HashSet commonParameterNames = - new HashSet - { - "Verbose", - "Debug", - "ErrorAction", - "WarningAction", - "InformationAction", - "ErrorVariable", - "WarningVariable", - "InformationVariable", - "OutVariable", - "OutBuffer", - "PipelineVariable", - }; + private static readonly ConcurrentDictionary commonParameterNames = + new ConcurrentDictionary(); + + static ParameterSetSignature() + { + commonParameterNames.TryAdd("Verbose", true); + commonParameterNames.TryAdd("Debug", true); + commonParameterNames.TryAdd("ErrorAction", true); + commonParameterNames.TryAdd("WarningAction", true); + commonParameterNames.TryAdd("InformationAction", true); + commonParameterNames.TryAdd("ErrorVariable", true); + commonParameterNames.TryAdd("WarningVariable", true); + commonParameterNames.TryAdd("InformationVariable", true); + commonParameterNames.TryAdd("OutVariable", true); + commonParameterNames.TryAdd("OutBuffer", true); + commonParameterNames.TryAdd("PipelineVariable", true); + } #region Properties /// @@ -91,7 +97,7 @@ public ParameterSetSignature(CommandParameterSetInfo commandParamInfoSet) List parameterInfo = new List(); foreach (CommandParameterInfo commandParameterInfo in commandParamInfoSet.Parameters) { - if (!commonParameterNames.Contains(commandParameterInfo.Name)) + if (!commonParameterNames.ContainsKey(commandParameterInfo.Name)) { parameterInfo.Add(new ParameterInfo(commandParameterInfo)); } @@ -122,7 +128,7 @@ public class ParameterInfo /// Gets the position of the parameter /// public int Position { get; internal set; } - + /// /// Gets a boolean for whetheer or not the parameter is required /// diff --git a/src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs similarity index 97% rename from src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs rename to src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index 762cc002b..1f754a235 100644 --- a/src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -7,14 +7,15 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices.Symbols +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// Provides an IDocumentSymbolProvider implementation for /// enumerating test symbols in Pester test (tests.ps1) files. /// - public class PesterDocumentSymbolProvider : FeatureProviderBase, IDocumentSymbolProvider + public class PesterDocumentSymbolProvider : IDocumentSymbolProvider { IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( diff --git a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs new file mode 100644 index 000000000..3a9d5b2a9 --- /dev/null +++ b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs @@ -0,0 +1,89 @@ +// +// 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; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Services.Symbols +{ + /// + /// Provides an IDocumentSymbolProvider implementation for + /// enumerating symbols in .psd1 files. + /// + public class PsdDocumentSymbolProvider : IDocumentSymbolProvider + { + IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( + ScriptFile scriptFile) + { + if ((scriptFile.FilePath != null && + scriptFile.FilePath.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) || + IsPowerShellDataFileAst(scriptFile.ScriptAst)) + { + var findHashtableSymbolsVisitor = new FindHashtableSymbolsVisitor(); + scriptFile.ScriptAst.Visit(findHashtableSymbolsVisitor); + return findHashtableSymbolsVisitor.SymbolReferences; + } + + return Enumerable.Empty(); + } + + /// + /// 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; + } + } +} diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs new file mode 100644 index 000000000..0ad1f20ea --- /dev/null +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs @@ -0,0 +1,77 @@ +// +// 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; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Services.Symbols +{ + /// + /// Provides an IDocumentSymbolProvider implementation for + /// enumerating symbols in script (.psd1, .psm1) files. + /// + public class ScriptDocumentSymbolProvider : 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 + FindSymbolsInDocument( + scriptFile.ScriptAst, + this.powerShellVersion); + } + + return Enumerable.Empty(); + } + + /// + /// 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; + } + } +} diff --git a/src/PowerShellEditorServices/Language/ScriptExtent.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs similarity index 97% rename from src/PowerShellEditorServices/Language/ScriptExtent.cs rename to src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs index 58f3ab84a..6abff2626 100644 --- a/src/PowerShellEditorServices/Language/ScriptExtent.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs @@ -6,7 +6,7 @@ using System; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// Provides a default IScriptExtent implementation diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs new file mode 100644 index 000000000..a8272d375 --- /dev/null +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -0,0 +1,90 @@ +// +// 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.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; + +namespace Microsoft.PowerShell.EditorServices.Services.Symbols +{ + /// + /// 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, + PowerShellContextService powerShellContext) + { + SymbolDetails symbolDetails = new SymbolDetails + { + SymbolReference = symbolReference + }; + + switch (symbolReference.SymbolType) + { + case 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; + return symbolDetails; + } + } + + symbolDetails.DisplayString = "function " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Parameter: + // TODO: Get parameter help + symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Variable: + symbolDetails.DisplayString = symbolReference.SymbolName; + return symbolDetails; + + default: + return symbolDetails; + } + } + + #endregion + } +} diff --git a/src/PowerShellEditorServices/Language/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs similarity index 96% rename from src/PowerShellEditorServices/Language/SymbolReference.cs rename to src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index af0551a9e..02a808c2d 100644 --- a/src/PowerShellEditorServices/Language/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -3,11 +3,11 @@ // 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; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// A class that holds the type, name, script extent, and source line of a symbol diff --git a/src/PowerShellEditorServices/Language/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs similarity index 94% rename from src/PowerShellEditorServices/Language/SymbolType.cs rename to src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 2dba9a0a0..29352e24b 100644 --- a/src/PowerShellEditorServices/Language/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// A way to define symbols on a higher level diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs similarity index 57% rename from src/PowerShellEditorServices/Language/LanguageService.cs rename to src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 486306b58..f3c9c53b0 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -1,10 +1,8 @@ -// +// // 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; @@ -13,71 +11,50 @@ 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Provides a high-level service for performing code completion and /// navigation operations on PowerShell scripts. /// - public class LanguageService + public class SymbolsService { #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 PowerShellContextService _powerShellContextService; + private readonly WorkspaceService _workspaceService; 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 + /// Constructs an instance of the SymbolsService 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) + /// An ILoggerFactory implementation used for writing log messages. + public SymbolsService( + ILoggerFactory factory, + PowerShellContextService powerShellContextService, + WorkspaceService workspaceService) { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - _powerShellContext = powerShellContext; - _logger = logger; - - _cmdletToAliasDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); - _aliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + _workspaceService = workspaceService; _documentSymbolProviders = new IDocumentSymbolProvider[] { - new ScriptDocumentSymbolProvider(powerShellContext.LocalPowerShellVersion.Version), + new ScriptDocumentSymbolProvider(VersionUtils.PSVersion), new PsdDocumentSymbolProvider(), new PesterDocumentSymbolProvider() }; @@ -85,104 +62,27 @@ public LanguageService( #endregion - #region Public Methods - /// - /// Gets completions for a statement contained in the given - /// script file at the specified line and column position. + /// Finds all the symbols in a file. /// - /// - /// 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) + /// The ScriptFile in which the symbol can be located. + /// + public List FindSymbolsInFile(ScriptFile scriptFile) { 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) + var foundOccurrences = new List(); + foreach (IDocumentSymbolProvider symbolProvider in _documentSymbolProviders) { - if (completion.CompletionText.Equals(entryName)) + foreach (SymbolReference reference in symbolProvider.ProvideDocumentSymbols(scriptFile)) { - return completion; + reference.SourceLine = scriptFile.GetLine(reference.ScriptRegion.StartLineNumber); + reference.FilePath = scriptFile.FilePath; + foundOccurrences.Add(reference); } } - // If we found no completions, return null - return null; + return foundOccurrences; } /// @@ -213,6 +113,107 @@ public SymbolReference FindSymbolAtLocation( return symbolReference; } + /// + /// 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 List FindReferencesOfSymbol( + SymbolReference foundSymbol, + ScriptFile[] referencedFiles, + WorkspaceService workspace) + { + if (foundSymbol == null) + { + return null; + } + + // NOTE: we use to make sure aliases were loaded but took it out because we needed the pipeline thread. + + // We want to look for references first in referenced files, hence we use ordered dictionary + // 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]; + + IEnumerable references = AstOperations.FindReferencesOfSymbol( + file.ScriptAst, + foundSymbol, + needsAliases: false); + + foreach (SymbolReference reference in references) + { + try + { + reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); + } + catch (ArgumentOutOfRangeException e) + { + reference.SourceLine = string.Empty; + _logger.LogException("Found reference is out of range in script file", e); + } + reference.FilePath = file.FilePath; + symbolReferences.Add(reference); + } + } + + return symbolReferences; + } + + /// + /// 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 IReadOnlyList FindOccurrencesInFile( + ScriptFile file, + int symbolLineNumber, + int symbolColumnNumber) + { + SymbolReference foundSymbol = AstOperations.FindSymbolAtPosition( + file.ScriptAst, + symbolLineNumber, + symbolColumnNumber); + + if (foundSymbol == null) + { + return null; + } + + return AstOperations.FindReferencesOfSymbol( + file.ScriptAst, + foundSymbol, + needsAliases: false).ToArray(); + } + /// /// Finds a function definition in the script given a file location /// @@ -254,7 +255,6 @@ public async Task FindSymbolDetailsAtLocationAsync( int lineNumber, int columnNumber) { - SymbolDetails symbolDetails = null; SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, @@ -263,135 +263,72 @@ public async Task FindSymbolDetailsAtLocationAsync( if (symbolReference == null) { - // TODO #21: Return Result return null; } symbolReference.FilePath = scriptFile.FilePath; - symbolDetails = - await SymbolDetails.CreateAsync( - symbolReference, - _powerShellContext); + SymbolDetails symbolDetails = await SymbolDetails.CreateAsync( + symbolReference, + _powerShellContextService); return symbolDetails; } /// - /// Finds all the symbols in a file. + /// Finds the parameter set hints of a specific command (determined by a given file location) /// - /// The ScriptFile in which the symbol can be located. - /// - public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile) + /// 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, + PowerShellContextService powerShellContext) { - 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 - }; - } + SymbolReference foundSymbol = + AstOperations.FindCommandAtPosition( + file.ScriptAst, + lineNumber, + columnNumber); - /// - /// 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); + CommandInfo commandInfo = + await CommandHelpers.GetCommandInfoAsync( + foundSymbol.SymbolName, + powerShellContext); - foreach (ScriptFile scriptFile in referencedFiles) + if (commandInfo == null) { - fileMap[scriptFile.FilePath] = scriptFile; + return null; } - foreach (string filePath in workspace.EnumeratePSFiles()) + try { - 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; - } + IEnumerable commandParamSets = commandInfo.ParameterSets; + return new ParameterSetSignatures(commandParamSets, foundSymbol); } - - var symbolReferences = new List(); - foreach (object fileName in fileMap.Keys) + catch (RuntimeException e) { - var file = (ScriptFile)fileMap[fileName]; - await _aliasHandle.WaitAsync(); - try - { - - IEnumerable references = AstOperations.FindReferencesOfSymbol( - file.ScriptAst, - foundSymbol, - _cmdletToAliasDictionary, - _aliasToCmdletDictionary); + // 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.LogException("RuntimeException encountered while accessing command parameter sets", e); - 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 null; } - - return new FindReferencesResult + catch (InvalidOperationException) { - SymbolFileOffset = symbolOffset, - SymbolName = foundSymbol.SymbolName, - FoundReferences = symbolReferences - }; + // 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; + } } /// @@ -400,19 +337,16 @@ public async Task FindReferencesOfSymbolAsync( /// /// 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( + public async Task GetDefinitionOfSymbolAsync( ScriptFile sourceFile, - SymbolReference foundSymbol, - Workspace workspace) + SymbolReference foundSymbol) { Validate.IsNotNull(nameof(sourceFile), sourceFile); Validate.IsNotNull(nameof(foundSymbol), foundSymbol); - Validate.IsNotNull(nameof(workspace), workspace); ScriptFile[] referencedFiles = - workspace.ExpandScriptReferences( + _workspaceService.ExpandScriptReferences( sourceFile); var filesSearched = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -437,7 +371,7 @@ public async Task GetDefinitionOfSymbolAsync( if (foundSymbol.SymbolType == SymbolType.Function) { // Dot-sourcing is parsed as a "Function" Symbol. - string dotSourcedPath = GetDotSourcedPath(foundSymbol, workspace, scriptFile); + string dotSourcedPath = GetDotSourcedPath(foundSymbol, scriptFile); if (scriptFile.FilePath == dotSourcedPath) { foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath); @@ -451,7 +385,7 @@ public async Task GetDefinitionOfSymbolAsync( if (foundDefinition == null) { // Get a list of all powershell files in the workspace path - IEnumerable allFiles = workspace.EnumeratePSFiles(); + IEnumerable allFiles = _workspaceService.EnumeratePSFiles(); foreach (string file in allFiles) { if (filesSearched.Contains(file)) @@ -459,11 +393,9 @@ public async Task GetDefinitionOfSymbolAsync( continue; } - Token[] tokens = null; - ParseError[] parseErrors = null; foundDefinition = AstOperations.FindDefinitionOfSymbol( - Parser.ParseFile(file, out tokens, out parseErrors), + Parser.ParseFile(file, out Token[] tokens, out ParseError[] parseErrors), foundSymbol); filesSearched.Add(file); @@ -472,7 +404,6 @@ public async Task GetDefinitionOfSymbolAsync( foundDefinition.FilePath = file; break; } - } } @@ -483,165 +414,99 @@ public async Task GetDefinitionOfSymbolAsync( CommandInfo cmdInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, - _powerShellContext); + _powerShellContextService); foundDefinition = FindDeclarationForBuiltinCommand( cmdInfo, - foundSymbol, - workspace); + foundSymbol); } - return foundDefinition != null ? - new GetDefinitionResult(foundDefinition) : - null; + return foundDefinition; } /// /// 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) + private string GetDotSourcedPath(SymbolReference symbol, ScriptFile scriptFile) { string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"')); string psScriptRoot = Path.GetDirectoryName(scriptFile.FilePath); - return workspace.ResolveRelativeScriptPath(psScriptRoot, + return _workspaceService.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) + private SymbolReference FindDeclarationForBuiltinCommand( + CommandInfo commandInfo, + SymbolReference foundSymbol) { - SymbolReference foundSymbol = - AstOperations.FindSymbolAtPosition( - file.ScriptAst, - lineNumber, - columnNumber); - - if (foundSymbol == null) + if (commandInfo == null) { return null; } - // find all references, and indicate that looking for aliases is not needed - IEnumerable symbolOccurrences = - AstOperations - .FindReferencesOfSymbol( - file.ScriptAst, - foundSymbol, - false); + ScriptFile[] nestedModuleFiles = + GetBuiltinCommandScriptFiles( + commandInfo.Module); - return new FindOccurrencesResult + SymbolReference foundDefinition = null; + foreach (ScriptFile nestedModuleFile in nestedModuleFiles) { - 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); + foundDefinition = AstOperations.FindDefinitionOfSymbol( + nestedModuleFile.ScriptAst, + foundSymbol); - if (foundSymbol == null) - { - return null; + if (foundDefinition != null) + { + foundDefinition.FilePath = nestedModuleFile.FilePath; + break; + } } - CommandInfo commandInfo = - await CommandHelpers.GetCommandInfoAsync( - foundSymbol.SymbolName, - _powerShellContext); + return foundDefinition; + } - if (commandInfo == null) + private ScriptFile[] GetBuiltinCommandScriptFiles( + PSModuleInfo moduleInfo) + { + if (moduleInfo == null) { - return null; + return new ScriptFile[0]; } - 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); + string modPath = moduleInfo.Path; + List scriptFiles = new List(); + ScriptFile newFile; - return null; - } - catch (InvalidOperationException) + // find any files where the moduleInfo's path ends with ps1 or psm1 + // and add it to allowed script files + if (modPath.EndsWith(@".ps1", StringComparison.OrdinalIgnoreCase) || + modPath.EndsWith(@".psm1", StringComparison.OrdinalIgnoreCase)) { - // 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; + newFile = _workspaceService.GetFile(modPath); + newFile.IsAnalysisEnabled = false; + scriptFiles.Add(newFile); } - } - /// - /// 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) + if (moduleInfo.NestedModules.Count > 0) { - return null; + foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules) + { + string nestedModPath = nestedInfo.Path; + if (nestedModPath.EndsWith(@".ps1", StringComparison.OrdinalIgnoreCase) || + nestedModPath.EndsWith(@".psm1", StringComparison.OrdinalIgnoreCase)) + { + newFile = _workspaceService.GetFile(nestedModPath); + newFile.IsAnalysisEnabled = false; + scriptFiles.Add(newFile); + } + } } - 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; + return scriptFiles.ToArray(); } /// @@ -668,8 +533,7 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( IEnumerable foundAsts = scriptFile.ScriptAst.FindAll( ast => { - var fdAst = ast as FunctionDefinitionAst; - if (fdAst == null) + if (!(ast is FunctionDefinitionAst fdAst)) { return false; } @@ -720,168 +584,21 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return null; } - #endregion - - #region Private Fields - /// - /// Gets all aliases found in the runspace + /// Gets the function defined on a given line. /// - 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) + /// 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) { - 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; - } - } + Ast functionDefinitionAst = scriptFile.ScriptAst.Find( + ast => ast is FunctionDefinitionAst && ast.Extent.StartLineNumber == lineNumber, + true); - return minAst; + return functionDefinitionAst as FunctionDefinitionAst; } - - #endregion } } diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs similarity index 90% rename from src/PowerShellEditorServices/Language/AstOperations.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 354e5f188..4dc284340 100644 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -3,22 +3,21 @@ // 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.Diagnostics; using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Language; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Management.Automation.Language; -using System.Management.Automation.Runspaces; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { - using System.Management.Automation; - using System.Management.Automation.Language; - /// /// Provides common operations for the syntax tree of a parsed script. /// @@ -27,11 +26,12 @@ 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); + .GetType("System.Management.Automation.Language.InternalScriptPosition") + .GetMethod("CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly SemaphoreSlim s_completionHandle = AsyncUtils.CreateSimpleLockingSemaphore(); + // TODO: BRING THIS BACK /// /// Gets completions for the symbol found in the Ast at /// the given file offset. @@ -56,11 +56,11 @@ internal static class AstOperations /// A CommandCompletion instance that contains completions for the /// symbol at the given offset. /// - static public async Task GetCompletionsAsync( + public static async Task GetCompletionsAsync( Ast scriptAst, Token[] currentTokens, int fileOffset, - PowerShellContext powerShellContext, + PowerShellContextService powerShellContext, ILogger logger, CancellationToken cancellationToken) { @@ -75,8 +75,7 @@ static public async Task GetCompletionsAsync( scriptAst.Extent.StartScriptPosition, new object[] { fileOffset }); - logger.Write( - LogLevel.Verbose, + logger.LogTrace( string.Format( "Getting completions at offset {0} (line: {1}, column: {2})", fileOffset, @@ -96,7 +95,7 @@ static public async Task GetCompletionsAsync( if (powerShellContext.IsCurrentRunspaceOutOfProcess()) { using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken)) - using (PowerShell powerShell = PowerShell.Create()) + using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create()) { powerShell.Runspace = runspaceHandle.Runspace; stopwatch.Start(); @@ -112,7 +111,7 @@ static public async Task GetCompletionsAsync( finally { stopwatch.Stop(); - logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); + logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); } } } @@ -130,7 +129,7 @@ await powerShellContext.InvokeOnPipelineThreadAsync( powershell: pwsh); }); stopwatch.Stop(); - logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); + logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); return commandCompletion; } @@ -148,7 +147,7 @@ await powerShellContext.InvokeOnPipelineThreadAsync( /// 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( + public static SymbolReference FindSymbolAtPosition( Ast scriptAst, int lineNumber, int columnNumber, @@ -172,7 +171,7 @@ static public SymbolReference FindSymbolAtPosition( /// 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) + public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumber, int columnNumber) { FindCommandVisitor commandVisitor = new FindCommandVisitor(lineNumber, columnNumber); scriptAst.Visit(commandVisitor); @@ -188,7 +187,7 @@ static public SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe /// Dictionary maping cmdlets to aliases for finding alias references /// Dictionary maping aliases to cmdlets for finding alias references /// - static public IEnumerable FindReferencesOfSymbol( + public static IEnumerable FindReferencesOfSymbol( Ast scriptAst, SymbolReference symbolReference, Dictionary> CmdletToAliasDictionary, @@ -214,7 +213,7 @@ static public IEnumerable FindReferencesOfSymbol( /// 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( + public static IEnumerable FindReferencesOfSymbol( ScriptBlockAst scriptAst, SymbolReference foundSymbol, bool needsAliases) @@ -232,7 +231,7 @@ static public IEnumerable FindReferencesOfSymbol( /// 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( + public static SymbolReference FindDefinitionOfSymbol( Ast scriptAst, SymbolReference symbolReference) { @@ -250,7 +249,7 @@ static public SymbolReference FindDefinitionOfSymbol( /// 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) + public static IEnumerable FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion) { IEnumerable symbolReferences = null; @@ -277,7 +276,7 @@ static public IEnumerable FindSymbolsInDocument(Ast scriptAst, /// /// The abstract syntax tree of the given script /// true if the AST represts a *.psd1 file, otherwise false - static public bool IsPowerShellDataFileAst(Ast ast) + public static bool IsPowerShellDataFileAst(Ast ast) { // sometimes we don't have reliable access to the filename // so we employ heuristics to check if the contents are @@ -332,7 +331,7 @@ static private bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap /// The abstract syntax tree of the given script /// Pre-calculated value of $PSScriptRoot /// - static public string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot) + public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot) { FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor(psScriptRoot); scriptAst.Visit(dotSourcedVisitor); diff --git a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs similarity index 91% rename from src/PowerShellEditorServices/Language/FindCommandVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs index 5e5fca624..6d033188b 100644 --- a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs @@ -6,15 +6,15 @@ using System.Linq; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The vistior used to find the commandAst of a specific location in an AST /// internal class FindCommandVisitor : AstVisitor { - private int lineNumber; - private int columnNumber; + private readonly int lineNumber; + private readonly int columnNumber; public SymbolReference FoundCommandReference { get; private set; } @@ -37,7 +37,7 @@ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) if (currentLine.Length >= trueEndColumnNumber) { // Get the text left in the line after the command's extent - string remainingLine = + string remainingLine = currentLine.Substring( commandAst.Extent.EndColumnNumber); @@ -48,8 +48,8 @@ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) // 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 + + trueEndColumnNumber = + commandAst.Extent.EndColumnNumber + (preTrimLength - postTrimLength) + 1; } @@ -70,12 +70,12 @@ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) } /// - /// Is the position of the given location is in the range of the start + /// 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 + /// 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) { diff --git a/src/PowerShellEditorServices/Language/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs similarity index 97% rename from src/PowerShellEditorServices/Language/FindDeclarationVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index f8ecffc2e..91f3671ff 100644 --- a/src/PowerShellEditorServices/Language/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -4,11 +4,9 @@ // using System; -using System.Collections.Generic; -using System.Linq; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The visitor used to find the definition of a symbol @@ -44,7 +42,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun // instead of the the start column of 'function' and create new extent for the functionName int startColumnNumber = functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name) + 1; + functionDefinitionAst.Name, StringComparison.OrdinalIgnoreCase) + 1; IScriptExtent nameExtent = new ScriptExtent() { diff --git a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs similarity index 98% rename from src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs index c0295a228..11443e327 100644 --- a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs @@ -8,7 +8,7 @@ using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The vistor used to find the dont sourced files in an AST diff --git a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs similarity index 99% rename from src/PowerShellEditorServices/Language/FindReferencesVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 2b7b160ea..7ca206dae 100644 --- a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The visitor used to find the references of a symbol in a script's AST diff --git a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs similarity index 98% rename from src/PowerShellEditorServices/Language/FindSymbolVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 404f8d190..6d1629869 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -5,7 +5,7 @@ using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The visitor used to find the the symbol at a specfic location in the AST diff --git a/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs similarity index 96% rename from src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index 96f5c335c..c7f36544e 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The visitor used to find all the symbols (function and class defs) in the AST. @@ -120,8 +120,7 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) foreach (var kvp in hashtableAst.KeyValuePairs) { - var keyStrConstExprAst = kvp.Item1 as StringConstantExpressionAst; - if (keyStrConstExprAst != null) + if (kvp.Item1 is StringConstantExpressionAst keyStrConstExprAst) { IScriptExtent nameExtent = new ScriptExtent() { diff --git a/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs similarity index 98% rename from src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs rename to src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs index 03628ee3e..44f1560d4 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Symbols { // TODO: Restore this when we figure out how to support multiple // PS versions in the new PSES-as-a-module world (issue #276) diff --git a/src/PowerShellEditorServices/Workspace/BufferPosition.cs b/src/PowerShellEditorServices/Services/TextDocument/BufferPosition.cs similarity index 98% rename from src/PowerShellEditorServices/Workspace/BufferPosition.cs rename to src/PowerShellEditorServices/Services/TextDocument/BufferPosition.cs index effdd7660..1cdf6cf67 100644 --- a/src/PowerShellEditorServices/Workspace/BufferPosition.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/BufferPosition.cs @@ -5,7 +5,7 @@ using System.Diagnostics; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Provides details about a position in a file buffer. All diff --git a/src/PowerShellEditorServices/Workspace/BufferRange.cs b/src/PowerShellEditorServices/Services/TextDocument/BufferRange.cs similarity index 98% rename from src/PowerShellEditorServices/Workspace/BufferRange.cs rename to src/PowerShellEditorServices/Services/TextDocument/BufferRange.cs index 147eed042..dbc577b05 100644 --- a/src/PowerShellEditorServices/Workspace/BufferRange.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/BufferRange.cs @@ -6,7 +6,7 @@ using System; using System.Diagnostics; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Provides details about a range between two positions in diff --git a/src/PowerShellEditorServices/Language/CompletionResults.cs b/src/PowerShellEditorServices/Services/TextDocument/CompletionResults.cs similarity index 98% rename from src/PowerShellEditorServices/Language/CompletionResults.cs rename to src/PowerShellEditorServices/Services/TextDocument/CompletionResults.cs index fc8d2eb00..e267eb0a1 100644 --- a/src/PowerShellEditorServices/Language/CompletionResults.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/CompletionResults.cs @@ -10,7 +10,7 @@ using System.Text.RegularExpressions; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Provides the results of a single code completion request. @@ -243,8 +243,7 @@ internal static CompletionDetails Create( /// True if the CompletionResults instances have the same details. public override bool Equals(object obj) { - CompletionDetails otherDetails = obj as CompletionDetails; - if (otherDetails == null) + if (!(obj is CompletionDetails otherDetails)) { return false; } @@ -306,6 +305,7 @@ private static CompletionType ConvertCompletionResultType( return CompletionType.Type; case CompletionResultType.Keyword: + case CompletionResultType.DynamicKeyword: return CompletionType.Keyword; case CompletionResultType.ProviderContainer: diff --git a/src/PowerShellEditorServices/Workspace/FileChange.cs b/src/PowerShellEditorServices/Services/TextDocument/FileChange.cs similarity index 95% rename from src/PowerShellEditorServices/Workspace/FileChange.cs rename to src/PowerShellEditorServices/Services/TextDocument/FileChange.cs index 79f6925ea..c407ae881 100644 --- a/src/PowerShellEditorServices/Workspace/FileChange.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/FileChange.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Contains details relating to a content change in an open file. diff --git a/src/PowerShellEditorServices/Workspace/FilePosition.cs b/src/PowerShellEditorServices/Services/TextDocument/FilePosition.cs similarity index 98% rename from src/PowerShellEditorServices/Workspace/FilePosition.cs rename to src/PowerShellEditorServices/Services/TextDocument/FilePosition.cs index a7c9036c7..8b0e4e970 100644 --- a/src/PowerShellEditorServices/Workspace/FilePosition.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/FilePosition.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Provides details and operations for a buffer position in a diff --git a/src/PowerShellEditorServices/Language/FoldingReference.cs b/src/PowerShellEditorServices/Services/TextDocument/FoldingReference.cs similarity index 94% rename from src/PowerShellEditorServices/Language/FoldingReference.cs rename to src/PowerShellEditorServices/Services/TextDocument/FoldingReference.cs index 2b8a14502..477c27d88 100644 --- a/src/PowerShellEditorServices/Language/FoldingReference.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/FoldingReference.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// A class that holds the information for a foldable region of text in a document @@ -36,7 +37,7 @@ public class FoldingReference: IComparable /// /// Describes the kind of the folding range such as `comment' or 'region'. /// - public string Kind { get; set; } + public FoldingRangeKind? Kind { get; set; } /// /// A custom comparable method which can properly sort FoldingReference objects @@ -58,7 +59,7 @@ public int CompareTo(FoldingReference that) { if (this.EndCharacter > that.EndCharacter) { return 1; } // They're the same range, but what about kind - return string.Compare(this.Kind, that.Kind); + return that.Kind.Value - this.Kind.Value; } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs new file mode 100644 index 000000000..80180ca61 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs @@ -0,0 +1,123 @@ +// +// 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class CodeActionHandler : ICodeActionHandler + { + private static readonly CodeActionKind[] s_supportedCodeActions = new[] + { + CodeActionKind.QuickFix + }; + + private readonly CodeActionRegistrationOptions _registrationOptions; + + private readonly ILogger _logger; + + private readonly AnalysisService _analysisService; + + private CodeActionCapability _capability; + + public CodeActionHandler(ILoggerFactory factory, AnalysisService analysisService) + { + _logger = factory.CreateLogger(); + _analysisService = analysisService; + _registrationOptions = new CodeActionRegistrationOptions + { + DocumentSelector = new DocumentSelector(new DocumentFilter() { Language = "powershell" }), + CodeActionKinds = s_supportedCodeActions + }; + } + + public CodeActionRegistrationOptions GetRegistrationOptions() + { + return _registrationOptions; + } + + public void SetCapability(CodeActionCapability capability) + { + _capability = capability; + } + + public async Task Handle(CodeActionParams request, CancellationToken cancellationToken) + { + IReadOnlyDictionary corrections = await _analysisService.GetMostRecentCodeActionsForFileAsync(request.TextDocument.Uri.ToString()); + + if (corrections == null) + { + // TODO: Find out if we can cache this empty value + return new CommandOrCodeActionContainer(); + } + + var codeActions = 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. + foreach (Diagnostic diagnostic in request.Context.Diagnostics) + { + if (diagnostic.Code.IsLong) + { + _logger.LogWarning( + $"textDocument/codeAction skipping diagnostic with non-string code {diagnostic.Code.Long}: {diagnostic.Source} {diagnostic.Message}"); + } + else if (string.IsNullOrEmpty(diagnostic.Code.String)) + { + _logger.LogWarning( + $"textDocument/codeAction skipping diagnostic with empty Code field: {diagnostic.Source} {diagnostic.Message}"); + + continue; + } + + + string diagnosticId = AnalysisService.GetUniqueIdFromDiagnostic(diagnostic); + if (corrections.TryGetValue(diagnosticId, out MarkerCorrection correction)) + { + codeActions.Add(new Command() + { + Title = correction.Name, + Name = "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 (Diagnostic diagnostic in request.Context.Diagnostics) + { + if (!diagnostic.Code.IsString || string.IsNullOrEmpty(diagnostic.Code.String)) { continue; } + + if (string.Equals(diagnostic.Source, "PSScriptAnalyzer", StringComparison.OrdinalIgnoreCase) && + !ruleNamesProcessed.Contains(diagnostic.Code.String)) + { + ruleNamesProcessed.Add(diagnostic.Code.String); + + codeActions.Add( + new Command + { + Title = $"Show documentation for \"{diagnostic.Code}\"", + Name = "PowerShell.ShowCodeActionDocumentation", + Arguments = JArray.FromObject(new[] { diagnostic.Code }) + }); + } + } + + return codeActions; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs new file mode 100644 index 000000000..a4c9d2ca8 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs @@ -0,0 +1,159 @@ +// +// 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; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.CodeLenses; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class CodeLensHandlers : ICodeLensHandler, ICodeLensResolveHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; + + private readonly ICodeLensProvider[] _providers; + + private CodeLensCapability _capability; + + public CodeLensHandlers(ILoggerFactory factory, SymbolsService symbolsService, WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _workspaceService = workspaceService; + _symbolsService = symbolsService; + _providers = new ICodeLensProvider[] + { + new ReferencesCodeLensProvider(_workspaceService, _symbolsService), + new PesterCodeLensProvider() + }; + } + + CodeLensRegistrationOptions IRegistration.GetRegistrationOptions() + { + return new CodeLensRegistrationOptions + { + DocumentSelector = _documentSelector, + ResolveProvider = true + }; + } + + public Task Handle(CodeLensParams request, CancellationToken cancellationToken) + { + ScriptFile scriptFile = _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile); + + return Task.FromResult(new CodeLensContainer(codeLensResults)); + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector, + }; + } + + public bool CanResolve(CodeLens value) + { + CodeLensData codeLensData = value.Data.ToObject(); + return value?.Data != null && _providers.Any(provider => provider.ProviderId.Equals(codeLensData.ProviderId)); + } + + public Task Handle(CodeLens request, CancellationToken cancellationToken) + { + // TODO: Catch deserializtion exception on bad object + CodeLensData codeLensData = request.Data.ToObject(); + + ICodeLensProvider originalProvider = + _providers.FirstOrDefault( + provider => provider.ProviderId.Equals(codeLensData.ProviderId)); + + ScriptFile scriptFile = + _workspaceService.GetFile( + codeLensData.Uri); + + var resolvedCodeLens = originalProvider.ResolveCodeLens(request, scriptFile); + return Task.FromResult(resolvedCodeLens); + } + + public void SetCapability(CodeLensCapability capability) + { + _capability = capability; + } + + /// + /// 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. + private CodeLens[] ProvideCodeLenses(ScriptFile scriptFile) + { + return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile)) + .SelectMany(codeLens => codeLens) + .ToArray(); + } + + /// + /// 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. + /// + private 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.LogTrace( + $"Invocation of provider '{provider.GetType().Name}' completed in {invokeTimer.ElapsedMilliseconds}ms."); + } + catch (Exception e) + { + this._logger.LogException( + $"Exception caught while invoking provider {provider.GetType().Name}:", + e); + } + } + + return providerResults; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs new file mode 100644 index 000000000..1f2f2e09a --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -0,0 +1,335 @@ +// +// 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.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class CompletionHandler : ICompletionHandler, ICompletionResolveHandler + { + const int DefaultWaitTimeoutMilliseconds = 5000; + private readonly CompletionItem[] s_emptyCompletionResult = new CompletionItem[0]; + + private readonly ILogger _logger; + private readonly PowerShellContextService _powerShellContextService; + private readonly WorkspaceService _workspaceService; + + private CompletionResults _mostRecentCompletions; + + private int _mostRecentRequestLine; + + private int _mostRecentRequestOffest; + + private string _mostRecentRequestFile; + + private CompletionCapability _capability; + + public CompletionHandler( + ILoggerFactory factory, + PowerShellContextService powerShellContextService, + WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _powerShellContextService = powerShellContextService; + _workspaceService = workspaceService; + } + + public CompletionRegistrationOptions GetRegistrationOptions() + { + return new CompletionRegistrationOptions + { + DocumentSelector = new DocumentSelector(new DocumentFilter { Language = "powershell" }), + ResolveProvider = true, + TriggerCharacters = new[] { ".", "-", ":", "\\" } + }; + } + + public async Task Handle(CompletionParams request, CancellationToken cancellationToken) + { + int cursorLine = (int) request.Position.Line + 1; + int cursorColumn = (int) request.Position.Character + 1; + + ScriptFile scriptFile = + _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + CompletionResults completionResults = + await 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); + } + } + + return new CompletionList(completionItems); + } + + public bool CanResolve(CompletionItem value) + { + return value.Kind == CompletionItemKind.Function; + } + + // Handler for "completionItem/resolve". In VSCode this is fired when a completion item is highlighted in the completion list. + public async Task Handle(CompletionItem request, CancellationToken cancellationToken) + { + // Get the documentation for the function + CommandInfo commandInfo = + await CommandHelpers.GetCommandInfoAsync( + request.Label, + _powerShellContextService); + + if (commandInfo != null) + { + request.Documentation = + await CommandHelpers.GetCommandSynopsisAsync( + commandInfo, + _powerShellContextService); + } + + // Send back the updated CompletionItem + return request; + } + + public void SetCapability(CompletionCapability capability) + { + _capability = capability; + } + + /// + /// 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 = null; + using (var cts = new CancellationTokenSource(DefaultWaitTimeoutMilliseconds)) + { + commandCompletion = + await AstOperations.GetCompletionsAsync( + scriptFile.ScriptAst, + scriptFile.ScriptTokens, + fileOffset, + _powerShellContextService, + _logger, + cts.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.LogError( + $"Caught exception while trying to create CompletionResults:\n\n{e.ToString()}"); + + return new CompletionResults(); + } + } + + 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 && EndsWithQuote(completionText)) + { + // 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 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 bool EndsWithQuote(string text) + { + if (string.IsNullOrEmpty(text)) + { + return false; + } + + char lastChar = text[text.Length - 1]; + return lastChar == '"' || lastChar == '\''; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs new file mode 100644 index 000000000..64033c564 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -0,0 +1,109 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class DefinitionHandler : IDefinitionHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; + + private DefinitionCapability _capability; + + public DefinitionHandler( + ILoggerFactory factory, + SymbolsService symbolsService, + WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _symbolsService = symbolsService; + _workspaceService = workspaceService; + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector + }; + } + + public async Task Handle(DefinitionParams request, CancellationToken cancellationToken) + { + ScriptFile scriptFile = + _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + SymbolReference foundSymbol = + _symbolsService.FindSymbolAtLocation( + scriptFile, + (int) request.Position.Line + 1, + (int) request.Position.Character + 1); + + List definitionLocations = new List(); + if (foundSymbol != null) + { + SymbolReference foundDefinition = await _symbolsService.GetDefinitionOfSymbolAsync( + scriptFile, + foundSymbol); + + if (foundDefinition != null) + { + definitionLocations.Add( + new LocationOrLocationLink( + new Location + { + Uri = PathUtils.ToUri(foundDefinition.FilePath), + Range = GetRangeFromScriptRegion(foundDefinition.ScriptRegion) + })); + } + } + + return new LocationOrLocationLinks(definitionLocations); + } + + public void SetCapability(DefinitionCapability capability) + { + _capability = capability; + } + + private 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 + } + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs new file mode 100644 index 000000000..2790f8409 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class DocumentHighlightHandler : IDocumentHighlightHandler + { + private static readonly DocumentHighlightContainer s_emptyHighlightContainer = new DocumentHighlightContainer(); + + private readonly ILogger _logger; + + private readonly WorkspaceService _workspaceService; + + private readonly SymbolsService _symbolsService; + + private readonly TextDocumentRegistrationOptions _registrationOptions; + + private DocumentHighlightCapability _capability; + + public DocumentHighlightHandler( + ILoggerFactory loggerFactory, + WorkspaceService workspaceService, + SymbolsService symbolService) + { + _logger = loggerFactory.CreateLogger(); + _workspaceService = workspaceService; + _symbolsService = symbolService; + _registrationOptions = new TextDocumentRegistrationOptions() + { + DocumentSelector = new DocumentSelector(new DocumentFilter() { Language = "powershell" } ) + }; + _logger.LogInformation("highlight handler loaded"); + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return _registrationOptions; + } + + public Task Handle( + DocumentHighlightParams request, + CancellationToken cancellationToken) + { + ScriptFile scriptFile = _workspaceService.GetFile(PathUtils.FromUri(request.TextDocument.Uri)); + + IReadOnlyList symbolOccurrences = _symbolsService.FindOccurrencesInFile( + scriptFile, + (int)request.Position.Line, + (int)request.Position.Character); + + if (symbolOccurrences == null) + { + return Task.FromResult(s_emptyHighlightContainer); + } + + var highlights = new DocumentHighlight[symbolOccurrences.Count]; + for (int i = 0; i < symbolOccurrences.Count; i++) + { + highlights[i] = new DocumentHighlight + { + Kind = DocumentHighlightKind.Write, // TODO: Which symbol types are writable? + Range = symbolOccurrences[i].ScriptRegion.ToRange() + }; + } + + return Task.FromResult(new DocumentHighlightContainer(highlights)); + } + + public void SetCapability(DocumentHighlightCapability capability) + { + _capability = capability; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs new file mode 100644 index 000000000..52ba0660b --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -0,0 +1,201 @@ +// +// 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.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class DocumentSymbolHandler : IDocumentSymbolHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly WorkspaceService _workspaceService; + + private readonly IDocumentSymbolProvider[] _providers; + + private DocumentSymbolCapability _capability; + + public DocumentSymbolHandler(ILoggerFactory factory, ConfigurationService configurationService, WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _workspaceService = workspaceService; + _providers = new IDocumentSymbolProvider[] + { + new ScriptDocumentSymbolProvider( + VersionUtils.PSVersion), + new PsdDocumentSymbolProvider(), + new PesterDocumentSymbolProvider() + }; + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector, + }; + } + + public Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken) + { + ScriptFile scriptFile = + _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + IEnumerable foundSymbols = + this.ProvideDocumentSymbols(scriptFile); + + SymbolInformationOrDocumentSymbol[] symbols = null; + + string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); + + if (foundSymbols != null) + { + symbols = + foundSymbols + .Select(r => + { + return new SymbolInformationOrDocumentSymbol(new SymbolInformation + { + ContainerName = containerName, + Kind = GetSymbolKind(r.SymbolType), + Location = new Location + { + Uri = PathUtils.ToUri(r.FilePath), + Range = GetRangeFromScriptRegion(r.ScriptRegion) + }, + Name = GetDecoratedSymbolName(r) + }); + }) + .ToArray(); + } + else + { + symbols = new SymbolInformationOrDocumentSymbol[0]; + } + + + return Task.FromResult(new SymbolInformationOrDocumentSymbolContainer(symbols)); + } + + public void SetCapability(DocumentSymbolCapability capability) + { + _capability = capability; + } + + private IEnumerable ProvideDocumentSymbols( + ScriptFile scriptFile) + { + return + this.InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile)) + .SelectMany(r => r); + } + + /// + /// 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.LogTrace( + $"Invocation of provider '{provider.GetType().Name}' completed in {invokeTimer.ElapsedMilliseconds}ms."); + } + catch (Exception e) + { + this._logger.LogException( + $"Exception caught while invoking provider {provider.GetType().Name}:", + e); + } + } + + return providerResults; + } + + private static SymbolKind GetSymbolKind(SymbolType symbolType) + { + switch (symbolType) + { + case SymbolType.Configuration: + case SymbolType.Function: + case SymbolType.Workflow: + return SymbolKind.Function; + + default: + return SymbolKind.Variable; + } + } + + private 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; + } + + private 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 + } + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs new file mode 100644 index 000000000..dbb554fb0 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs @@ -0,0 +1,83 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class FoldingRangeHandler : IFoldingRangeHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter() + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly ConfigurationService _configurationService; + private readonly WorkspaceService _workspaceService; + + private FoldingRangeCapability _capability; + + public FoldingRangeHandler(ILoggerFactory factory, ConfigurationService configurationService, WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _configurationService = configurationService; + _workspaceService = workspaceService; + } + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions() + { + DocumentSelector = _documentSelector, + }; + } + + public Task> Handle(FoldingRangeRequestParam request, CancellationToken cancellationToken) + { + // TODO Should be using dynamic registrations + if (!_configurationService.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 scenario of "no backing file" probably doesn't + // warrant the extra effort. + if (!_workspaceService.TryGetFile(request.TextDocument.Uri.ToString(), out ScriptFile scriptFile)) { return null; } + + var result = new List(); + + // If we're showing the last line, decrement the Endline of all regions by one. + int endLineOffset = _configurationService.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 Task.FromResult(new Container(result)); + } + + public void SetCapability(FoldingRangeCapability capability) + { + _capability = capability; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs new file mode 100644 index 000000000..aabdeb615 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs @@ -0,0 +1,177 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class DocumentFormattingHandler : IDocumentFormattingHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter() + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly AnalysisService _analysisService; + private readonly ConfigurationService _configurationService; + private readonly WorkspaceService _workspaceService; + private DocumentFormattingCapability _capability; + + public DocumentFormattingHandler(ILoggerFactory factory, AnalysisService analysisService, ConfigurationService configurationService, WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _analysisService = analysisService; + _configurationService = configurationService; + _workspaceService = workspaceService; + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector + }; + } + + public async Task Handle(DocumentFormattingParams request, CancellationToken cancellationToken) + { + var scriptFile = _workspaceService.GetFile(request.TextDocument.Uri.ToString()); + var pssaSettings = _configurationService.CurrentSettings.CodeFormatting.GetPSSASettingsHashtable( + (int)request.Options.TabSize, + request.Options.InsertSpaces); + + + // TODO raise an error event in case format returns null + string formattedScript; + Range editRange; + 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 _analysisService.FormatAsync( + scriptFile.Contents, + pssaSettings, + null); + formattedScript = formattedScript ?? scriptFile.Contents; + + return new TextEditContainer(new TextEdit + { + NewText = formattedScript, + Range = editRange + }); + } + + public void SetCapability(DocumentFormattingCapability capability) + { + _capability = capability; + } + } + + internal class DocumentRangeFormattingHandler : IDocumentRangeFormattingHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter() + { + Pattern = "**/*.ps*1" + } + ); + + private readonly ILogger _logger; + private readonly AnalysisService _analysisService; + private readonly ConfigurationService _configurationService; + private readonly WorkspaceService _workspaceService; + private DocumentRangeFormattingCapability _capability; + + public DocumentRangeFormattingHandler(ILoggerFactory factory, AnalysisService analysisService, ConfigurationService configurationService, WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _analysisService = analysisService; + _configurationService = configurationService; + _workspaceService = workspaceService; + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector + }; + } + + public async Task Handle(DocumentRangeFormattingParams request, CancellationToken cancellationToken) + { + var scriptFile = _workspaceService.GetFile(request.TextDocument.Uri.ToString()); + var pssaSettings = _configurationService.CurrentSettings.CodeFormatting.GetPSSASettingsHashtable( + (int)request.Options.TabSize, + request.Options.InsertSpaces); + + // TODO raise an error event in case format returns null; + string formattedScript; + Range editRange; + 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 + } + }; + + Range range = request.Range; + var rangeList = range == null ? null : new int[] { + (int)range.Start.Line + 1, + (int)range.Start.Character + 1, + (int)range.End.Line + 1, + (int)range.End.Character + 1}; + + formattedScript = await _analysisService.FormatAsync( + scriptFile.Contents, + pssaSettings, + rangeList); + formattedScript = formattedScript ?? scriptFile.Contents; + + return new TextEditContainer(new TextEdit + { + NewText = formattedScript, + Range = editRange + }); + } + + public void SetCapability(DocumentRangeFormattingCapability capability) + { + _capability = capability; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs new file mode 100644 index 000000000..8a107ed42 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -0,0 +1,111 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class HoverHandler : IHoverHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; + private readonly PowerShellContextService _powerShellContextService; + + private HoverCapability _capability; + + public HoverHandler( + ILoggerFactory factory, + SymbolsService symbolsService, + WorkspaceService workspaceService, + PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _symbolsService = symbolsService; + _workspaceService = workspaceService; + _powerShellContextService = powerShellContextService; + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector, + }; + } + + public async Task Handle(HoverParams request, CancellationToken cancellationToken) + { + ScriptFile scriptFile = + _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + SymbolDetails symbolDetails = + await _symbolsService.FindSymbolDetailsAtLocationAsync( + scriptFile, + (int) request.Position.Line + 1, + (int) request.Position.Character + 1); + + List symbolInfo = new List(); + Range symbolRange = null; + + if (symbolDetails != null) + { + symbolInfo.Add(new MarkedString("PowerShell", symbolDetails.DisplayString)); + + if (!string.IsNullOrEmpty(symbolDetails.Documentation)) + { + symbolInfo.Add(new MarkedString("markdown", symbolDetails.Documentation)); + } + + symbolRange = GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion); + } + + return new Hover + { + Contents = new MarkedStringsOrMarkupContent(symbolInfo), + Range = symbolRange + }; + } + + public void SetCapability(HoverCapability capability) + { + _capability = capability; + } + + private 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 + } + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs new file mode 100644 index 000000000..c72ce141e --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -0,0 +1,106 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + class ReferencesHandler : IReferencesHandler + { + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter() + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; + private ReferencesCapability _capability; + + public ReferencesHandler(ILoggerFactory factory, SymbolsService symbolsService, WorkspaceService workspaceService) + { + _logger = factory.CreateLogger(); + _symbolsService = symbolsService; + _workspaceService = workspaceService; + } + + public TextDocumentRegistrationOptions GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions + { + DocumentSelector = _documentSelector + }; + } + + public async Task Handle(ReferenceParams request, CancellationToken cancellationToken) + { + ScriptFile scriptFile = + _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + SymbolReference foundSymbol = + _symbolsService.FindSymbolAtLocation( + scriptFile, + (int)request.Position.Line + 1, + (int)request.Position.Character + 1); + + List referencesResult = + _symbolsService.FindReferencesOfSymbol( + foundSymbol, + _workspaceService.ExpandScriptReferences(scriptFile), + _workspaceService); + + var locations = new List(); + + if (referencesResult != null) + { + foreach (SymbolReference foundReference in referencesResult) + { + locations.Add(new Location + { + Uri = PathUtils.ToUri(foundReference.FilePath), + Range = GetRangeFromScriptRegion(foundReference.ScriptRegion) + }); + } + } + + return new LocationContainer(locations); + } + + public void SetCapability(ReferencesCapability capability) + { + _capability = capability; + } + + private 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 + } + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs new file mode 100644 index 000000000..eac0303ab --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs @@ -0,0 +1,118 @@ +// +// 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.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class SignatureHelpHandler : ISignatureHelpHandler + { + private static readonly SignatureInformation[] s_emptySignatureResult = new SignatureInformation[0]; + + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter() + { + Language = "powershell" + } + ); + + private readonly ILogger _logger; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; + private readonly PowerShellContextService _powerShellContextService; + + private SignatureHelpCapability _capability; + + public SignatureHelpHandler( + ILoggerFactory factory, + SymbolsService symbolsService, + WorkspaceService workspaceService, + PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _symbolsService = symbolsService; + _workspaceService = workspaceService; + _powerShellContextService = powerShellContextService; + } + + public SignatureHelpRegistrationOptions GetRegistrationOptions() + { + return new SignatureHelpRegistrationOptions + { + DocumentSelector = _documentSelector, + // A sane default of " ". We may be able to include others like "-". + TriggerCharacters = new Container(" ") + }; + } + + public async Task Handle(SignatureHelpParams request, CancellationToken cancellationToken) + { + ScriptFile scriptFile = + _workspaceService.GetFile( + request.TextDocument.Uri.ToString()); + + ParameterSetSignatures parameterSets = + await _symbolsService.FindParameterSetsInFileAsync( + scriptFile, + (int) request.Position.Line + 1, + (int) request.Position.Character + 1, + _powerShellContextService); + + 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, + }; + } + } + + return new SignatureHelp + { + Signatures = signatures, + ActiveParameter = null, + ActiveSignature = 0 + }; + } + + public void SetCapability(SignatureHelpCapability capability) + { + _capability = capability; + } + + private static ParameterInformation CreateParameterInfo(ParameterInfo parameterInfo) + { + return new ParameterInformation + { + Label = parameterInfo.Name, + Documentation = string.Empty + }; + } + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs new file mode 100644 index 000000000..375034533 --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs @@ -0,0 +1,172 @@ +// +// 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + class TextDocumentHandler : ITextDocumentSyncHandler + { + + private readonly ILogger _logger; + private readonly AnalysisService _analysisService; + private readonly WorkspaceService _workspaceService; + private readonly RemoteFileManagerService _remoteFileManagerService; + + private readonly DocumentSelector _documentSelector = new DocumentSelector( + new DocumentFilter() + { + Language = "powershell" + } + ); + + private SynchronizationCapability _capability; + + public TextDocumentSyncKind Change => TextDocumentSyncKind.Incremental; + + public TextDocumentHandler( + ILoggerFactory factory, + AnalysisService analysisService, + WorkspaceService workspaceService, + RemoteFileManagerService remoteFileManagerService) + { + _logger = factory.CreateLogger(); + _analysisService = analysisService; + _workspaceService = workspaceService; + _remoteFileManagerService = remoteFileManagerService; + } + + public Task Handle(DidChangeTextDocumentParams notification, CancellationToken token) + { + List changedFiles = new List(); + + // A text change notification can batch multiple change requests + foreach (TextDocumentContentChangeEvent textChange in notification.ContentChanges) + { + ScriptFile changedFile = _workspaceService.GetFile(notification.TextDocument.Uri.ToString()); + + changedFile.ApplyChange( + GetFileChangeDetails( + textChange.Range, + textChange.Text)); + + changedFiles.Add(changedFile); + } + + // TODO: Get all recently edited files in the workspace + _analysisService.RunScriptDiagnosticsAsync(changedFiles.ToArray()); + return Unit.Task; + } + + TextDocumentChangeRegistrationOptions IRegistration.GetRegistrationOptions() + { + return new TextDocumentChangeRegistrationOptions() + { + DocumentSelector = _documentSelector, + SyncKind = Change + }; + } + + public void SetCapability(SynchronizationCapability capability) + { + _capability = capability; + } + + public Task Handle(DidOpenTextDocumentParams notification, CancellationToken token) + { + ScriptFile openedFile = + _workspaceService.GetFileBuffer( + notification.TextDocument.Uri.ToString(), + notification.TextDocument.Text); + + // TODO: Get all recently edited files in the workspace + _analysisService.RunScriptDiagnosticsAsync(new ScriptFile[] { openedFile }); + + _logger.LogTrace("Finished opening document."); + return Unit.Task; + } + + TextDocumentRegistrationOptions IRegistration.GetRegistrationOptions() + { + return new TextDocumentRegistrationOptions() + { + DocumentSelector = _documentSelector, + }; + } + + public Task Handle(DidCloseTextDocumentParams notification, CancellationToken token) + { + // Find and close the file in the current session + var fileToClose = _workspaceService.GetFile(notification.TextDocument.Uri.ToString()); + + if (fileToClose != null) + { + _workspaceService.CloseFile(fileToClose); + _analysisService.ClearMarkers(fileToClose); + } + + _logger.LogTrace("Finished closing document."); + return Unit.Task; + } + + public async Task Handle(DidSaveTextDocumentParams notification, CancellationToken token) + { + ScriptFile savedFile = + _workspaceService.GetFile( + notification.TextDocument.Uri.ToString()); + + if (savedFile != null) + { + if (_remoteFileManagerService.IsUnderRemoteTempPath(savedFile.FilePath)) + { + await _remoteFileManagerService.SaveRemoteFileAsync(savedFile.FilePath); + } + } + return Unit.Value; + } + + TextDocumentSaveRegistrationOptions IRegistration.GetRegistrationOptions() + { + return new TextDocumentSaveRegistrationOptions() + { + DocumentSelector = _documentSelector, + IncludeText = true + }; + } + public TextDocumentAttributes GetTextDocumentAttributes(Uri uri) + { + return new TextDocumentAttributes(uri, "powershell"); + } + + 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 = (int)(changeRange.Start.Line + 1), + Offset = (int)(changeRange.Start.Character + 1), + EndLine = (int)(changeRange.End.Line + 1), + EndOffset = (int)(changeRange.End.Character + 1), + IsReload = false + }; + } + } +} diff --git a/src/PowerShellEditorServices/Workspace/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs similarity index 98% rename from src/PowerShellEditorServices/Workspace/ScriptFile.cs rename to src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs index 958778240..5b1b58bd5 100644 --- a/src/PowerShellEditorServices/Workspace/ScriptFile.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs @@ -9,10 +9,10 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; -using System.Runtime.InteropServices; +using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Contains the details and contents of an open script file. @@ -62,7 +62,7 @@ public string DocumentUri { return this.ClientFilePath == null ? string.Empty - : Workspace.ConvertPathToDocumentUri(this.ClientFilePath); + : WorkspaceService.ConvertPathToDocumentUri(this.ClientFilePath); } } @@ -163,7 +163,7 @@ public ScriptFile( this.FilePath = filePath; this.ClientFilePath = clientFilePath; this.IsAnalysisEnabled = true; - this.IsInMemory = Workspace.IsPathInMemory(filePath); + this.IsInMemory = WorkspaceService.IsPathInMemory(filePath); this.powerShellVersion = powerShellVersion; // SetFileContents() calls ParseFileContents() which initializes the rest of the properties. diff --git a/src/PowerShellEditorServices/Workspace/ScriptFileMarker.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs similarity index 88% rename from src/PowerShellEditorServices/Workspace/ScriptFileMarker.cs rename to src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs index 9eb73cb6c..9609b2cae 100644 --- a/src/PowerShellEditorServices/Workspace/ScriptFileMarker.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs @@ -3,14 +3,14 @@ // 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; +using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Contains details for a code correction which can be applied from a ScriptFileMarker. @@ -140,20 +140,21 @@ internal static ScriptFileMarker FromDiagnosticRecord(PSObject psObject) if (diagnosticRecord.SuggestedCorrections != null) { - var suggestedCorrections = diagnosticRecord.SuggestedCorrections as dynamic; - List editRegions = new List(); + var editRegions = new List(); string correctionMessage = null; - foreach (var suggestedCorrection in suggestedCorrections) + foreach (dynamic suggestedCorrection in diagnosticRecord.SuggestedCorrections) { - editRegions.Add(new ScriptRegion - { - File = diagnosticRecord.ScriptPath, - Text = suggestedCorrection.Text, - StartLineNumber = suggestedCorrection.StartLineNumber, - StartColumnNumber = suggestedCorrection.StartColumnNumber, - EndLineNumber = suggestedCorrection.EndLineNumber, - EndColumnNumber = suggestedCorrection.EndColumnNumber - }); + editRegions.Add( + new ScriptRegion( + diagnosticRecord.ScriptPath, + suggestedCorrection.Text, + startLineNumber: suggestedCorrection.StartLineNumber, + startColumnNumber: suggestedCorrection.StartColumnNumber, + endLineNumber: suggestedCorrection.EndLineNumber, + endColumnNumber: suggestedCorrection.EndColumnNumber, + startOffset: -1, + endOffset: -1)); + correctionMessage = suggestedCorrection.Description; } diff --git a/src/PowerShellEditorServices/Workspace/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs similarity index 65% rename from src/PowerShellEditorServices/Workspace/ScriptRegion.cs rename to src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 5717b1382..f18fcd6c4 100644 --- a/src/PowerShellEditorServices/Workspace/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -6,54 +6,116 @@ using System; using System.Management.Automation.Language; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// /// Contains details about a specific region of text in script file. /// public sealed class ScriptRegion : IScriptExtent { + #region Static Methods + + /// + /// 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( + scriptExtent.File, + scriptExtentText, + scriptExtent.StartLineNumber, + scriptExtent.StartColumnNumber, + scriptExtent.StartOffset, + scriptExtent.EndLineNumber, + scriptExtent.EndColumnNumber, + scriptExtent.EndOffset); + } + + #endregion + + #region Constructors + + public ScriptRegion( + string file, + string text, + int startLineNumber, + int startColumnNumber, + int startOffset, + int endLineNumber, + int endColumnNumber, + int endOffset) + { + File = file; + Text = text; + StartLineNumber = startLineNumber; + StartColumnNumber = startColumnNumber; + StartOffset = startOffset; + EndLineNumber = endLineNumber; + EndColumnNumber = endColumnNumber; + EndOffset = endOffset; + } + + #endregion + #region Properties /// /// Gets the file path of the script file in which this region is contained. /// - public string File { get; set; } + public string File { get; } /// /// Gets or sets the text that is contained within the region. /// - public string Text { get; set; } + public string Text { get; } /// /// Gets or sets the starting line number of the region. /// - public int StartLineNumber { get; set; } + public int StartLineNumber { get; } /// /// Gets or sets the starting column number of the region. /// - public int StartColumnNumber { get; set; } + public int StartColumnNumber { get; } /// /// Gets or sets the starting file offset of the region. /// - public int StartOffset { get; set; } + public int StartOffset { get; } /// /// Gets or sets the ending line number of the region. /// - public int EndLineNumber { get; set; } + public int EndLineNumber { get; } /// /// Gets or sets the ending column number of the region. /// - public int EndColumnNumber { get; set; } + public int EndColumnNumber { get; } /// /// Gets or sets the ending file offset of the region. /// - public int EndOffset { get; set; } + public int EndOffset { get; } /// /// Gets the starting IScriptPosition in the script. @@ -68,45 +130,5 @@ public sealed class ScriptRegion : IScriptExtent 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/Language/TokenOperations.cs b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs similarity index 94% rename from src/PowerShellEditorServices/Language/TokenOperations.cs rename to src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs index e62dca68c..4b3c7f6ab 100644 --- a/src/PowerShellEditorServices/Language/TokenOperations.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs @@ -3,12 +3,12 @@ // 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; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.TextDocument { /// @@ -19,7 +19,7 @@ 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; + private static readonly FoldingRangeKind? 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; @@ -112,7 +112,7 @@ internal static FoldingReferenceList FoldableReferences( // Processing for comment regions <# -> #> if (token.Extent.StartLineNumber != token.Extent.EndLineNumber) { - refList.SafeAdd(CreateFoldingReference(token, token, RegionKindComment)); + refList.SafeAdd(CreateFoldingReference(token, token, FoldingRangeKind.Comment)); continue; } @@ -130,7 +130,7 @@ internal static FoldingReferenceList FoldableReferences( // Mismatched regions in the script can cause bad stacks. if (tokenCommentRegionStack.Count > 0) { - refList.SafeAdd(CreateFoldingReference(tokenCommentRegionStack.Pop(), token, RegionKindRegion)); + refList.SafeAdd(CreateFoldingReference(tokenCommentRegionStack.Pop(), token, FoldingRangeKind.Region)); } continue; } @@ -140,7 +140,7 @@ internal static FoldingReferenceList FoldableReferences( int thisLine = token.Extent.StartLineNumber - 1; if ((blockStartToken != null) && (thisLine != blockNextLine)) { - refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment)); + refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, FoldingRangeKind.Comment)); blockStartToken = token; } if (blockStartToken == null) { blockStartToken = token; } @@ -151,7 +151,7 @@ internal static FoldingReferenceList FoldableReferences( // comment block simply ends at the end of document if (blockStartToken != null) { - refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment)); + refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, FoldingRangeKind.Comment)); } return refList; @@ -164,7 +164,7 @@ internal static FoldingReferenceList FoldableReferences( static private FoldingReference CreateFoldingReference( Token startToken, Token endToken, - string matchKind) + FoldingRangeKind? 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 @@ -184,7 +184,7 @@ static private FoldingReference CreateFoldingReference( static private FoldingReference CreateFoldingReference( Token startToken, int endLine, - string matchKind) + FoldingRangeKind? 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 diff --git a/src/PowerShellEditorServices/Services/Workspace/ConfigurationService.cs b/src/PowerShellEditorServices/Services/Workspace/ConfigurationService.cs new file mode 100644 index 000000000..e9d54359a --- /dev/null +++ b/src/PowerShellEditorServices/Services/Workspace/ConfigurationService.cs @@ -0,0 +1,15 @@ +// +// 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.Services.Configuration; + +namespace Microsoft.PowerShell.EditorServices.Services +{ + public class ConfigurationService + { + // This probably needs some sort of lock... or maybe LanguageServerSettings needs it. + public LanguageServerSettings CurrentSettings { get; } = new LanguageServerSettings(); + } +} diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs new file mode 100644 index 000000000..ae97d43ad --- /dev/null +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -0,0 +1,150 @@ +// +// 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Configuration; +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + internal class ConfigurationHandler : IDidChangeConfigurationHandler + { + private readonly ILogger _logger; + private readonly AnalysisService _analysisService; + private readonly WorkspaceService _workspaceService; + private readonly ConfigurationService _configurationService; + private readonly PowerShellContextService _powerShellContextService; + private DidChangeConfigurationCapability _capability; + private bool _profilesLoaded; + private bool _consoleReplStarted; + + public ConfigurationHandler( + ILoggerFactory factory, + WorkspaceService workspaceService, + AnalysisService analysisService, + ConfigurationService configurationService, + PowerShellContextService powerShellContextService) + { + _logger = factory.CreateLogger(); + _workspaceService = workspaceService; + _analysisService = analysisService; + _configurationService = configurationService; + _powerShellContextService = powerShellContextService; + } + + public object GetRegistrationOptions() + { + return null; + } + + public async Task Handle(DidChangeConfigurationParams request, CancellationToken cancellationToken) + { + LanguageServerSettingsWrapper incomingSettings = request.Settings.ToObject(); + if(incomingSettings == null) + { + return await Unit.Task; + } + + bool oldLoadProfiles = _configurationService.CurrentSettings.EnableProfileLoading; + bool oldScriptAnalysisEnabled = + _configurationService.CurrentSettings.ScriptAnalysis.Enable ?? false; + string oldScriptAnalysisSettingsPath = + _configurationService.CurrentSettings.ScriptAnalysis?.SettingsPath; + + _configurationService.CurrentSettings.Update( + incomingSettings.Powershell, + _workspaceService.WorkspacePath, + _logger); + + if (!this._profilesLoaded && + _configurationService.CurrentSettings.EnableProfileLoading && + oldLoadProfiles != _configurationService.CurrentSettings.EnableProfileLoading) + { + await _powerShellContextService.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 + _powerShellContextService.ConsoleReader.StartCommandLoop(); + this._consoleReplStarted = true; + } + + // If there is a new settings file path, restart the analyzer with the new settigs. + bool settingsPathChanged = false; + string newSettingsPath = _configurationService.CurrentSettings.ScriptAnalysis.SettingsPath; + if (!string.Equals(oldScriptAnalysisSettingsPath, newSettingsPath, StringComparison.OrdinalIgnoreCase)) + { + if (_analysisService != null) + { + _analysisService.SettingsPath = newSettingsPath; + settingsPathChanged = true; + } + } + + // If script analysis settings have changed we need to clear & possibly update the current diagnostic records. + if ((oldScriptAnalysisEnabled != _configurationService.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 (!_configurationService.CurrentSettings.ScriptAnalysis.Enable.Value || settingsPathChanged) + { + foreach (var scriptFile in _workspaceService.GetOpenedFiles()) + { + _analysisService.ClearMarkers(scriptFile); + } + } + } + + // 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 (incomingSettings.Files?.Exclude != null) + { + foreach(KeyValuePair patternEntry in incomingSettings.Files.Exclude) + { + if (patternEntry.Value) { excludeFilePatterns.Add(patternEntry.Key); } + } + } + if (incomingSettings.Search?.Exclude != null) + { + foreach(KeyValuePair patternEntry in incomingSettings.Files.Exclude) + { + if (patternEntry.Value && !excludeFilePatterns.Contains(patternEntry.Key)) { excludeFilePatterns.Add(patternEntry.Key); } + } + } + _workspaceService.ExcludeFilesGlob = excludeFilePatterns; + + // Convert the editor file search options to Workspace properties + if (incomingSettings.Search?.FollowSymlinks != null) + { + _workspaceService.FollowSymlinks = incomingSettings.Search.FollowSymlinks; + } + + return await Unit.Task; + } + + public void SetCapability(DidChangeConfigurationCapability capability) + { + _capability = capability; + } + } +} diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs new file mode 100644 index 000000000..da1ba6f21 --- /dev/null +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -0,0 +1,135 @@ +// +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + public class WorkspaceSymbolsHandler : IWorkspaceSymbolsHandler + { + private readonly ILogger _logger; + private readonly SymbolsService _symbolsService; + private readonly WorkspaceService _workspaceService; + private WorkspaceSymbolCapability _capability; + + public WorkspaceSymbolsHandler(ILoggerFactory loggerFactory, SymbolsService symbols, WorkspaceService workspace) { + _logger = loggerFactory.CreateLogger(); + _symbolsService = symbols; + _workspaceService = workspace; + } + + public object GetRegistrationOptions() + { + return null; + // throw new NotImplementedException(); + } + + public Task Handle(WorkspaceSymbolParams request, CancellationToken cancellationToken) + { + var symbols = new List(); + + foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) + { + List foundSymbols = + _symbolsService.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); + + foreach (SymbolReference foundOccurrence in foundSymbols) + { + if (!IsQueryMatch(request.Query, foundOccurrence.SymbolName)) + { + continue; + } + + var location = new Location + { + Uri = PathUtils.ToUri(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) + }); + } + } + _logger.LogWarning("Logging in a handler works now."); + + return Task.FromResult(new SymbolInformationContainer(symbols)); + } + + public void SetCapability(WorkspaceSymbolCapability capability) + { + _capability = capability; + } + + #region private Methods + + private bool IsQueryMatch(string query, string symbolName) + { + return symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; + } + + private 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; + } + + private 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 string GetDecoratedSymbolName(SymbolReference symbolReference) + { + string name = symbolReference.SymbolName; + + if (symbolReference.SymbolType == SymbolType.Configuration || + symbolReference.SymbolType == SymbolType.Function || + symbolReference.SymbolType == SymbolType.Workflow) + { + name += " { }"; + } + + return name; + } + + #endregion + } +} diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs similarity index 65% rename from src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs rename to src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs index 369c5ee45..dbdefc9de 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs @@ -1,20 +1,22 @@ -// +// // 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; -namespace Microsoft.PowerShell.EditorServices.Protocol.Server +namespace Microsoft.PowerShell.EditorServices.Services.Configuration { public class LanguageServerSettings { + private readonly object updateLock = new object(); public bool EnableProfileLoading { get; set; } public ScriptAnalysisSettings ScriptAnalysis { get; set; } @@ -37,19 +39,24 @@ public void Update( { 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); + lock (updateLock) + { + 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 { + private readonly object updateLock = new object(); + public bool? Enable { get; set; } public string SettingsPath { get; set; } @@ -66,50 +73,52 @@ public void Update( { if (settings != null) { - this.Enable = settings.Enable; + lock(updateLock) + { + this.Enable = settings.Enable; - string settingsPath = settings.SettingsPath; + string settingsPath = settings.SettingsPath; - try - { - if (string.IsNullOrWhiteSpace(settingsPath)) + try { - settingsPath = null; - } - else if (!Path.IsPathRooted(settingsPath)) - { - if (string.IsNullOrEmpty(workspaceRootPath)) + if (string.IsNullOrWhiteSpace(settingsPath)) { - // 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."); + settingsPath = null; } - else + else if (!Path.IsPathRooted(settingsPath)) { - settingsPath = Path.GetFullPath(Path.Combine(workspaceRootPath, 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.LogError( + "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 = settingsPath; + logger.LogTrace($"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.LogException( + $"Invalid Script Analyzer settings path - '{settingsPath}'.", + ex); - this.SettingsPath = null; + this.SettingsPath = null; + } } } } @@ -137,7 +146,7 @@ public enum CodeFormattingPreset OTBS, /// - /// Configure the formatting settings to resemble the Stroustrup brace style variant of K&R indent/brace style. + /// Configure the formatting settings to resemble the Stroustrup brace style variant of K&R indent/brace style. /// Stroustrup } @@ -170,7 +179,6 @@ public class CodeFormattingSettings /// > public CodeFormattingSettings() { - } /// @@ -250,59 +258,62 @@ public Hashtable GetPSSASettingsHashtable( private Hashtable GetCustomPSSASettingsHashtable(int tabSize, bool insertSpaces) { - var ruleConfigurations = 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} - }}, - }; + var ruleConfigurations = 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 } + }}, + }; + if (AutoCorrectAliases) { + // Empty hashtable required to activate the rule, + // since PSAvoidUsingCmdletAliases inherits from IScriptRule and not ConfigurableRule ruleConfigurations.Add("PSAvoidUsingCmdletAliases", new Hashtable()); } - return new Hashtable + return new Hashtable() { - {"IncludeRules", new string[] { - "PSPlaceCloseBrace", - "PSPlaceOpenBrace", - "PSUseConsistentWhitespace", - "PSUseConsistentIndentation", - "PSAlignAssignmentStatement", - "PSAvoidUsingCmdletAliases", + { "IncludeRules", new string[] { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement" }}, { "Rules", ruleConfigurations - }, + } }; } } @@ -332,11 +343,11 @@ public void Update( 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)); + logger.LogTrace(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)); + logger.LogTrace(string.Format("Using Code Folding ShowLastLine - {0}", this.ShowLastLine)); } } } diff --git a/src/PowerShellEditorServices/Workspace/WorkspaceFileSystemWrapper.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceFileSystemWrapper.cs similarity index 93% rename from src/PowerShellEditorServices/Workspace/WorkspaceFileSystemWrapper.cs rename to src/PowerShellEditorServices/Services/Workspace/WorkspaceFileSystemWrapper.cs index d90d0b4ec..89335db09 100644 --- a/src/PowerShellEditorServices/Workspace/WorkspaceFileSystemWrapper.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceFileSystemWrapper.cs @@ -3,14 +3,15 @@ // 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; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services.Workspace { /// @@ -85,7 +86,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (DirectoryNotFoundException e) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate directories in the path '{dirInfo.FullName}' due to it being an invalid path", e); @@ -93,7 +94,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (PathTooLongException e) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate directories in the path '{dirInfo.FullName}' due to the path being too long", e); @@ -101,7 +102,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate directories in the path '{dirInfo.FullName}' due to the path not being accessible", e); @@ -109,7 +110,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (Exception e) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate directories in the path '{dirInfo.FullName}' due to an exception", e); @@ -130,7 +131,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (DirectoryNotFoundException e) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate files in the path '{dirInfo.FullName}' due to it being an invalid path", e); @@ -138,7 +139,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (PathTooLongException e) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate files in the path '{dirInfo.FullName}' due to the path being too long", e); @@ -146,7 +147,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate files in the path '{dirInfo.FullName}' due to the path not being accessible", e); @@ -154,7 +155,7 @@ internal IEnumerable SafeEnumerateFileSystemInfos(DirectoryInfo } catch (Exception e) { - Logger.WriteHandledException( + Logger.LogHandledException( $"Could not enumerate files in the path '{dirInfo.FullName}' due to an exception", e); @@ -265,25 +266,25 @@ private DirectoryInfoBase SafeParentDirectory() } catch (DirectoryNotFoundException e) { - _fsWrapperFactory.Logger.WriteHandledException( + _fsWrapperFactory.Logger.LogHandledException( $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to it being an invalid path", e); } catch (PathTooLongException e) { - _fsWrapperFactory.Logger.WriteHandledException( + _fsWrapperFactory.Logger.LogHandledException( $"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( + _fsWrapperFactory.Logger.LogHandledException( $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to the path not being accessible", e); } catch (Exception e) { - _fsWrapperFactory.Logger.WriteHandledException( + _fsWrapperFactory.Logger.LogHandledException( $"Could not get parent of '{_concreteDirectoryInfo.FullName}' due to an exception", e); } @@ -342,25 +343,25 @@ private DirectoryInfoBase SafeParentDirectory() } catch (DirectoryNotFoundException e) { - _fsWrapperFactory.Logger.WriteHandledException( + _fsWrapperFactory.Logger.LogHandledException( $"Could not get parent of '{_concreteFileInfo.FullName}' due to it being an invalid path", e); } catch (PathTooLongException e) { - _fsWrapperFactory.Logger.WriteHandledException( + _fsWrapperFactory.Logger.LogHandledException( $"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( + _fsWrapperFactory.Logger.LogHandledException( $"Could not get parent of '{_concreteFileInfo.FullName}' due to the path not being accessible", e); } catch (Exception e) { - _fsWrapperFactory.Logger.WriteHandledException( + _fsWrapperFactory.Logger.LogHandledException( $"Could not get parent of '{_concreteFileInfo.FullName}' due to an exception", e); } diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs similarity index 90% rename from src/PowerShellEditorServices/Workspace/Workspace.cs rename to src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index e063877be..cae7e36a7 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -3,7 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.Linq; @@ -12,14 +11,18 @@ using System.Text; using System.Runtime.InteropServices; using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.PowerShell.EditorServices.Services.Workspace; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Services { /// /// Manages a "workspace" of script files that are open for a particular /// editing session. Also helps to navigate references between ScriptFiles. /// - public class Workspace + public class WorkspaceService { #region Private Fields @@ -48,16 +51,9 @@ public class Workspace "**/*" }; - private static readonly string[] s_supportedUriSchemes = new[] - { - "file", - "untitled", - "inmemory" - }; - - private ILogger logger; - private Version powerShellVersion; - private Dictionary workspaceFiles = new Dictionary(); + private readonly ILogger logger; + private readonly Version powerShellVersion; + private readonly Dictionary workspaceFiles = new Dictionary(); #endregion @@ -87,10 +83,10 @@ public class Workspace /// /// The version of PowerShell for which scripts will be parsed. /// An ILogger implementation used for writing log messages. - public Workspace(Version powerShellVersion, ILogger logger) + public WorkspaceService(ILoggerFactory factory) { - this.powerShellVersion = powerShellVersion; - this.logger = logger; + this.powerShellVersion = VersionUtils.PSVersion; + this.logger = factory.CreateLogger(); this.ExcludeFilesGlob = new List(); this.FollowSymlinks = true; } @@ -119,11 +115,11 @@ public ScriptFile CreateScriptFileFromFileBuffer(string filePath, string initial resolvedFilePath, filePath, initialBuffer, - this.powerShellVersion); + powerShellVersion); - this.workspaceFiles[keyName] = scriptFile; + workspaceFiles[keyName] = scriptFile; - this.logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); + logger.LogDebug("Opened file as in-memory buffer: " + resolvedFilePath); return scriptFile; } @@ -131,7 +127,7 @@ public ScriptFile CreateScriptFileFromFileBuffer(string filePath, string initial /// /// 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. + /// instead. /// /// The file path at which the script resides. /// @@ -149,8 +145,7 @@ public ScriptFile GetFile(string 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)) + if (!this.workspaceFiles.TryGetValue(keyName, out ScriptFile scriptFile)) { // This method allows FileNotFoundException to bubble up // if the file isn't found. @@ -167,7 +162,7 @@ public ScriptFile GetFile(string filePath) this.workspaceFiles.Add(keyName, scriptFile); } - this.logger.Write(LogLevel.Verbose, "Opened file on disk: " + resolvedFilePath); + this.logger.LogDebug("Opened file on disk: " + resolvedFilePath); } return scriptFile; @@ -180,18 +175,18 @@ public ScriptFile GetFile(string filePath) /// The out parameter that will contain the ScriptFile object. public bool TryGetFile(string filePath, out ScriptFile scriptFile) { - try + var fileUri = new Uri(filePath); + + switch (fileUri.Scheme) { - if (filePath.Contains(":/") // Quick heuristic to determine if we might have a URI - && !s_supportedUriSchemes.Contains(new Uri(filePath).Scheme)) - { + // List supported schemes here + case "file": + case "untitled": + break; + + default: scriptFile = null; return false; - } - } - catch - { - // If something goes wrong trying to check for URIs, just proceed to normal logic } try @@ -208,7 +203,7 @@ e is IOException || e is SecurityException || e is UnauthorizedAccessException) { - this.logger.WriteHandledException($"Failed to get file for {nameof(filePath)}: '{filePath}'", e); + this.logger.LogWarning($"Failed to get file for {nameof(filePath)}: '{filePath}'", e); scriptFile = null; return false; } @@ -240,8 +235,7 @@ public ScriptFile GetFileBuffer(string filePath, string initialBuffer) 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) + if (!this.workspaceFiles.TryGetValue(keyName, out ScriptFile scriptFile) && initialBuffer != null) { scriptFile = new ScriptFile( @@ -252,7 +246,7 @@ public ScriptFile GetFileBuffer(string filePath, string initialBuffer) this.workspaceFiles.Add(keyName, scriptFile); - this.logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); + this.logger.LogDebug("Opened file as in-memory buffer: " + resolvedFilePath); } return scriptFile; @@ -363,14 +357,14 @@ bool ignoreReparsePoints yield break; } - var matcher = new Microsoft.Extensions.FileSystemGlobbing.Matcher(); + var matcher = new 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, + VersionUtils.IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework, ignoreReparsePoints, logger ); @@ -385,8 +379,8 @@ bool ignoreReparsePoints #region Private Methods /// - /// Recusrively searches through referencedFiles in scriptFiles - /// and builds a Dictonary of the file references + /// Recursively searches through referencedFiles in scriptFiles + /// and builds a Dictionary of the file references /// /// Details an contents of "root" script file /// A Dictionary of referenced script files @@ -412,15 +406,14 @@ private void RecursivelyFindReferences( continue; } - this.logger.Write( - LogLevel.Verbose, + this.logger.LogDebug( 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)) + if (TryGetFile(resolvedScriptPath, out ScriptFile referencedFile)) { // Normalize the resolved script path and add it to the // referenced files list if it isn't there already @@ -440,7 +433,7 @@ internal string ResolveFilePath(string filePath) { if (filePath.StartsWith(@"file://")) { - filePath = Workspace.UnescapeDriveColon(filePath); + filePath = WorkspaceService.UnescapeDriveColon(filePath); // Client sent the path in URI format, extract the local path filePath = new Uri(filePath).LocalPath; } @@ -448,13 +441,13 @@ internal string ResolveFilePath(string filePath) // 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); + //filePath = PowerShellContext.UnescapeWildcardEscapedPath(filePath); // Get the absolute file path filePath = Path.GetFullPath(filePath); } - this.logger.Write(LogLevel.Verbose, "Resolved path: " + filePath); + this.logger.LogDebug("Resolved path: " + filePath); return filePath; } @@ -469,7 +462,7 @@ internal static bool IsPathInMemory(string filePath) // view of the current file or an untitled file. try { - // File system absoulute paths will have a URI scheme of file:. + // File system absolute 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; @@ -554,8 +547,7 @@ internal string ResolveRelativeScriptPath(string baseFilePath, string relativePa if (resolveException != null) { - this.logger.Write( - LogLevel.Error, + this.logger.LogError( $"Could not resolve relative script path\r\n" + $" baseFilePath = {baseFilePath}\r\n " + $" relativePath = {relativePath}\r\n\r\n" + @@ -687,7 +679,7 @@ public static string ConvertPathToDocumentUri(string path) docUriStrBld.Append(escapedPath).Replace("%2F", "/"); } - if (!Utils.IsNetCore) + if (!VersionUtils.IsNetCore) { // ' is not encoded by Uri.EscapeDataString in Windows PowerShell 5.x. // This is apparently a difference between .NET Framework and .NET Core. 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/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/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 index 5eba1b24f..65e92aa4f 100644 --- a/src/PowerShellEditorServices/Utility/AsyncLock.cs +++ b/src/PowerShellEditorServices/Utility/AsyncLock.cs @@ -125,4 +125,3 @@ public void Dispose() #endregion } } - diff --git a/src/PowerShellEditorServices/Utility/AsyncQueue.cs b/src/PowerShellEditorServices/Utility/AsyncQueue.cs index 85bbc1592..8125076b7 100644 --- a/src/PowerShellEditorServices/Utility/AsyncQueue.cs +++ b/src/PowerShellEditorServices/Utility/AsyncQueue.cs @@ -221,4 +221,3 @@ public T Dequeue(CancellationToken cancellationToken) #endregion } } - 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 index 08ffea60c..205332d83 100644 --- a/src/PowerShellEditorServices/Utility/Extensions.cs +++ b/src/PowerShellEditorServices/Utility/Extensions.cs @@ -28,7 +28,7 @@ public static string SafeToString(this object obj) } catch (Exception ex) { - str = $""; + str = $""; } return str; diff --git a/src/PowerShellEditorServices.Host/CodeLens/IScriptExtentExtensions.cs b/src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs similarity index 82% rename from src/PowerShellEditorServices.Host/CodeLens/IScriptExtentExtensions.cs rename to src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs index ca8f49d54..c48735c14 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/IScriptExtentExtensions.cs +++ b/src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs @@ -4,11 +4,11 @@ // using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Utility { - public static class IScriptExtentExtensions + internal static class IScriptExtentExtensions { public static Range ToRange(this IScriptExtent scriptExtent) { 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/LspDebugUtils.cs b/src/PowerShellEditorServices/Utility/LspDebugUtils.cs new file mode 100644 index 000000000..4423252ac --- /dev/null +++ b/src/PowerShellEditorServices/Utility/LspDebugUtils.cs @@ -0,0 +1,115 @@ +using System; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using OmniSharp.Extensions.DebugAdapter.Protocol.Models; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + public static class LspDebugUtils + { + public static Breakpoint CreateBreakpoint( + 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 CreateBreakpoint( + CommandBreakpointDetails breakpointDetails) + { + Validate.IsNotNull(nameof(breakpointDetails), breakpointDetails); + + return new Breakpoint + { + Verified = breakpointDetails.Verified, + Message = breakpointDetails.Message + }; + } + + public static Breakpoint CreateBreakpoint( + 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 + }; + } + + public static StackFrame CreateStackFrame( + StackFrameDetails stackFrame, + long 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 + }; + } + + 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 + }; + } + + public static Scope CreateScope(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)) + }; + } + + public static Variable CreateVariable(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/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 index 6b62122ec..13a8cccd0 100644 --- a/src/PowerShellEditorServices/Utility/PathUtils.cs +++ b/src/PowerShellEditorServices/Utility/PathUtils.cs @@ -1,20 +1,15 @@ -// +// // 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.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 + internal class PathUtils { /// /// The default path separator used by the base implementation of the providers. @@ -36,6 +31,42 @@ internal static class PathUtils internal static readonly char AlternatePathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '/' : '\\'; internal static readonly string AlternatePathSeparatorString = AlternatePathSeparator.ToString(); + public string WildcardUnescapePath(string path) + { + throw new NotImplementedException(); + } + + public static Uri ToUri(string filePath) + { + if (filePath.StartsWith("untitled", StringComparison.OrdinalIgnoreCase) || + filePath.StartsWith("inmemory", StringComparison.OrdinalIgnoreCase)) + { + return new Uri(filePath); + } + + filePath = filePath.Replace(":", "%3A").Replace("\\", "/"); + if (!filePath.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + { + return new Uri($"file:///{filePath}"); + } + + return new Uri($"file://{filePath}"); + } + + public static string FromUri(Uri uri) + { + if (uri.Segments.Length > 1) + { + // On windows of the Uri contains %3a local path + // doesn't come out as a proper windows path + if (uri.Segments[1].IndexOf("%3a", StringComparison.OrdinalIgnoreCase) > -1) + { + return FromUri(new Uri(uri.AbsoluteUri.Replace("%3a", ":").Replace("%3A", ":"))); + } + } + return uri.LocalPath; + } + /// /// Converts all alternate path separators to the current platform's main path separators. /// 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/Validate.cs b/src/PowerShellEditorServices/Utility/Validate.cs index 19aefe2c7..20fba09e3 100644 --- a/src/PowerShellEditorServices/Utility/Validate.cs +++ b/src/PowerShellEditorServices/Utility/Validate.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // diff --git a/src/PowerShellEditorServices/Utility/Utils.cs b/src/PowerShellEditorServices/Utility/VersionUtils.cs similarity index 61% rename from src/PowerShellEditorServices/Utility/Utils.cs rename to src/PowerShellEditorServices/Utility/VersionUtils.cs index cbac80891..5406ba1b6 100644 --- a/src/PowerShellEditorServices/Utility/Utils.cs +++ b/src/PowerShellEditorServices/Utility/VersionUtils.cs @@ -7,12 +7,12 @@ using System.Reflection; using System.Runtime.InteropServices; -namespace Microsoft.PowerShell.EditorServices +namespace Microsoft.PowerShell.EditorServices.Utility { /// /// General purpose common utilities to prevent reimplementation. /// - internal static class Utils + internal static class VersionUtils { /// /// True if we are running on .NET Core, false otherwise. @@ -24,6 +24,11 @@ internal static class Utils /// public static Version PSVersion { get; } = PowerShellReflectionUtils.PSVersion; + /// + /// Get's the Edition of PowerShell being used. + /// + public static string PSEdition { get; } = PowerShellReflectionUtils.PSEdition; + /// /// True if we are running in Windows PowerShell, false otherwise. /// @@ -43,13 +48,21 @@ internal static class Utils internal static class PowerShellReflectionUtils { - private static readonly Assembly s_psRuntimeAssembly = typeof(System.Management.Automation.Runspaces.Runspace).Assembly; - private static readonly PropertyInfo s_psVersionProperty = s_psRuntimeAssembly.GetType("System.Management.Automation.PSVersionInfo") + private static readonly Type s_psVersionInfoType = typeof(System.Management.Automation.Runspaces.Runspace).Assembly.GetType("System.Management.Automation.PSVersionInfo"); + private static readonly PropertyInfo s_psVersionProperty = s_psVersionInfoType .GetProperty("PSVersion", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + private static readonly PropertyInfo s_psEditionProperty = s_psVersionInfoType + .GetProperty("PSEdition", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); /// - /// Get's the Version of PowerShell being used. + /// Get's the Version of PowerShell being used. Note: this will get rid of the SemVer 2.0 suffix because apparently + /// that property is added as a note property and it is not there when we reflect. /// public static Version PSVersion { get; } = s_psVersionProperty.GetValue(null) as Version; + + /// + /// Get's the Edition of PowerShell being used. + /// + public static string PSEdition { get; } = s_psEditionProperty.GetValue(null) as string; } } diff --git a/test/Pester/EditorServices.Integration.Tests.ps1 b/test/Pester/EditorServices.Integration.Tests.ps1 deleted file mode 100644 index d5ea605c6..000000000 --- a/test/Pester/EditorServices.Integration.Tests.ps1 +++ /dev/null @@ -1,86 +0,0 @@ - -$script:ExceptionRegex = [regex]::new('\s*Exception: (.*)$', 'Compiled,Multiline,IgnoreCase') -function ReportLogErrors -{ - param( - [Parameter()][string]$LogPath, - - [Parameter()][ref]<#[int]#>$FromIndex = 0, - - [Parameter()][string[]]$IgnoreException = @() - ) - - $logEntries = Parse-PsesLog $LogPath | - Where-Object Index -ge $FromIndex.Value - - # Update the index to the latest in the log - $FromIndex.Value = ($FromIndex.Value,$errorLogs.Index | Measure-Object -Maximum).Maximum - - $errorLogs = $logEntries | - Where-Object LogLevel -eq Error | - Where-Object { - $match = $script:ExceptionRegex.Match($_.Message.Data) - - (-not $match) -or ($match.Groups[1].Value.Trim() -notin $IgnoreException) - } - - if ($errorLogs) - { - $errorLogs | ForEach-Object { Write-Error "ERROR from PSES log: $($_.Message.Data)" } - } -} - -Describe "Loading and running PowerShellEditorServices" { - BeforeAll { - Import-Module -Force "$PSScriptRoot/../../module/PowerShellEditorServices" - Import-Module -Force "$PSScriptRoot/../../tools/PsesPsClient/out/PsesPsClient" - Import-Module -Force "$PSScriptRoot/../../tools/PsesLogAnalyzer" - - $logIdx = 0 - $psesServer = Start-PsesServer - $client = Connect-PsesServer -PipeName $psesServer.SessionDetails.languageServicePipeName - } - - # This test MUST be first - It "Starts and responds to an initialization request" { - $request = Send-LspInitializeRequest -Client $client - $response = Get-LspResponse -Client $client -Id $request.Id - $response.Id | Should -BeExactly $request.Id - - ReportLogErrors -LogPath $psesServer.LogPath -FromIndex ([ref]$logIdx) - } - - # This test MUST be last - It "Shuts down the process properly" { - $request = Send-LspShutdownRequest -Client $client - $response = Get-LspResponse -Client $client -Id $request.Id - $response.Id | Should -BeExactly $request.Id - $response.Result | Should -BeNull - # TODO: The server seems to stay up waiting for the debug connection - # $psesServer.PsesProcess.HasExited | Should -BeTrue - - # We close the process here rather than in an AfterAll - # since errors can occur and we want to test for them. - # Naturally this depends on Pester executing tests in order. - - # We also have to dispose of everything properly, - # which means we have to use these cascading try/finally statements - try - { - $psesServer.PsesProcess.Kill() - } - finally - { - try - { - $psesServer.PsesProcess.Dispose() - } - finally - { - $client.Dispose() - } - } - - ReportLogErrors -LogPath $psesServer.LogPath -FromIndex ([ref]$logIdx) - } -} diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs new file mode 100644 index 000000000..fcf10bee3 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Client.Processes; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace PowerShellEditorServices.Test.E2E +{ + public class LSPTestsFixture : TestsFixture + { + public override bool IsDebugAdapterTests => false; + + public LanguageClient LanguageClient { get; private set; } + public List Diagnostics { get; set; } + + public async override Task CustomInitializeAsync( + ILoggerFactory factory, + StdioServerProcess process) + { + LanguageClient = new LanguageClient(factory, process); + + DirectoryInfo testdir = + Directory.CreateDirectory(Path.Combine(s_binDir, Path.GetRandomFileName())); + + await LanguageClient.Initialize(testdir.FullName); + + // Make sure Script Analysis is enabled because we'll need it in the tests. + LanguageClient.Workspace.DidChangeConfiguration(JObject.Parse(@" +{ + ""PowerShell"": { + ""ScriptAnalysis"": { + ""Enable"": true + } + } +} +")); + + Diagnostics = new List(); + LanguageClient.TextDocument.OnPublishDiagnostics((uri, diagnostics) => + { + Diagnostics.AddRange(diagnostics); + }); + } + + public override async Task DisposeAsync() + { + await LanguageClient.Shutdown(); + await _psesProcess.Stop(); + LanguageClient?.Dispose(); + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs new file mode 100644 index 000000000..776b84a40 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -0,0 +1,826 @@ +// +// 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.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Handlers; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Xunit; +using Xunit.Abstractions; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +namespace PowerShellEditorServices.Test.E2E +{ + public class LanguageServerProtocolMessageTests : IClassFixture, IDisposable + { + private readonly static string s_binDir = + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + private static bool s_registeredOnLogMessage; + + private readonly LanguageClient LanguageClient; + private readonly List Diagnostics; + private readonly string PwshExe; + private readonly ITestOutputHelper _output; + + public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixture data) + { + Diagnostics = new List(); + LanguageClient = data.LanguageClient; + Diagnostics = data.Diagnostics; + PwshExe = TestsFixture.PwshExe; + Diagnostics.Clear(); + + _output = output; + + if (!s_registeredOnLogMessage) + { + LanguageClient.Window.OnLogMessage((message, messageType) => + { + _output.WriteLine($"{messageType.ToString()}: {message}"); + }); + + s_registeredOnLogMessage = true; + } + } + + public void Dispose() + { + Diagnostics.Clear(); + } + + private string NewTestFile(string script, bool isPester = false) + { + string fileExt = isPester ? ".Tests.ps1" : ".ps1"; + string filePath = Path.Combine(s_binDir, Path.GetRandomFileName() + fileExt); + File.WriteAllText(filePath, script); + + LanguageClient.SendNotification("textDocument/didOpen", new DidOpenTextDocumentParams + { + TextDocument = new TextDocumentItem + { + LanguageId = "powershell", + Version = 0, + Text = script, + Uri = new Uri(filePath) + } + }); + + // Give PSES a chance to run what it needs to run. + Thread.Sleep(1000); + + return filePath; + } + + private async Task WaitForDiagnostics() + { + // Wait for PSSA to finish. + int i = 0; + while(Diagnostics.Count == 0) + { + if(i >= 10) + { + throw new InvalidDataException("No diagnostics showed up after 20s."); + } + + await Task.Delay(2000); + i++; + } + } + + [Fact] + public async Task CanSendPowerShellGetVersionRequest() + { + PowerShellVersion details + = await LanguageClient.SendRequest("powerShell/getVersion", new GetVersionParams()); + + if(PwshExe == "powershell") + { + Assert.Equal("Desktop", details.Edition); + } + else + { + Assert.Equal("Core", details.Edition); + } + } + + [Fact] + public async Task CanSendWorkspaceSymbolRequest() + { + + NewTestFile(@" +function CanSendWorkspaceSymbolRequest { + Write-Host 'hello' +} +"); + + SymbolInformationContainer symbols = await LanguageClient.SendRequest( + "workspace/symbol", + new WorkspaceSymbolParams + { + Query = "CanSendWorkspaceSymbolRequest" + }); + + SymbolInformation symbol = Assert.Single(symbols); + Assert.Equal("CanSendWorkspaceSymbolRequest { }", symbol.Name); + } + + [Fact] + public async Task CanReceiveDiagnosticsFromFileOpen() + { + NewTestFile("$a = 4"); + await WaitForDiagnostics(); + + Diagnostic diagnostic = Assert.Single(Diagnostics); + Assert.Equal("PSUseDeclaredVarsMoreThanAssignments", diagnostic.Code); + } + + [Fact] + public async Task CanReceiveDiagnosticsFromConfigurationChange() + { + NewTestFile("gci | % { $_ }"); + await WaitForDiagnostics(); + + // NewTestFile doesn't clear diagnostic notifications so we need to do that for this test. + Diagnostics.Clear(); + + try + { + LanguageClient.SendNotification("workspace/didChangeConfiguration", + new DidChangeConfigurationParams + { + Settings = JToken.Parse(@" +{ + ""PowerShell"": { + ""ScriptAnalysis"": { + ""Enable"": false + } + } +} +") + }); + + Assert.Empty(Diagnostics); + } + finally + { + LanguageClient.SendNotification("workspace/didChangeConfiguration", + new DidChangeConfigurationParams + { + Settings = JToken.Parse(@" +{ + ""PowerShell"": { + ""ScriptAnalysis"": { + ""Enable"": true + } + } +} +") + }); + } + } + + [Fact] + public async Task CanSendFoldingRangeRequest() + { + string scriptPath = NewTestFile(@"gci | % { +$_ + +@"" + $_ +""@ +}"); + + Container foldingRanges = + await LanguageClient.SendRequest>( + "textDocument/foldingRange", + new FoldingRangeRequestParam + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + } + }); + + Assert.Collection(foldingRanges.OrderBy(f => f.StartLine), + range1 => + { + Assert.Equal(0, range1.StartLine); + Assert.Equal(8, range1.StartCharacter); + Assert.Equal(5, range1.EndLine); + Assert.Equal(1, range1.EndCharacter); + }, + range2 => + { + Assert.Equal(3, range2.StartLine); + Assert.Equal(0, range2.StartCharacter); + Assert.Equal(4, range2.EndLine); + Assert.Equal(2, range2.EndCharacter); + }); + } + + [Fact] + public async Task CanSendFormattingRequest() + { + string scriptPath = NewTestFile(@" +gci | % { +Get-Process +} + +"); + + TextEditContainer textEdits = await LanguageClient.SendRequest( + "textDocument/formatting", + new DocumentFormattingParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + }, + Options = new FormattingOptions + { + TabSize = 4, + InsertSpaces = false + } + }); + + TextEdit textEdit = Assert.Single(textEdits); + + // If we have a tab, formatting ran. + Assert.Contains("\t", textEdit.NewText); + } + + [Fact] + public async Task CanSendRangeFormattingRequest() + { + string scriptPath = NewTestFile(@" +gci | % { +Get-Process +} + +"); + + TextEditContainer textEdits = await LanguageClient.SendRequest( + "textDocument/formatting", + new DocumentRangeFormattingParams + { + Range = new Range + { + Start = new Position + { + Line = 2, + Character = 0 + }, + End = new Position + { + Line = 3, + Character = 0 + } + }, + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + }, + Options = new FormattingOptions + { + TabSize = 4, + InsertSpaces = false + } + }); + + TextEdit textEdit = Assert.Single(textEdits); + + // If we have a tab, formatting ran. + Assert.Contains("\t", textEdit.NewText); + } + + [Fact] + public async Task CanSendDocumentSymbolRequest() + { + string scriptPath = NewTestFile(@" +function CanSendDocumentSymbolRequest { + +} + +CanSendDocumentSymbolRequest +"); + + SymbolInformationOrDocumentSymbolContainer symbolInformationOrDocumentSymbols = + await LanguageClient.SendRequest( + "textDocument/documentSymbol", + new DocumentSymbolParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + } + }); + + Assert.Collection(symbolInformationOrDocumentSymbols, + symInfoOrDocSym => { + Range range = symInfoOrDocSym.SymbolInformation.Location.Range; + + Assert.Equal(1, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(3, range.End.Line); + Assert.Equal(1, range.End.Character); + }); + } + + [Fact] + public async Task CanSendReferencesRequest() + { + string scriptPath = NewTestFile(@" +function CanSendReferencesRequest { + +} + +CanSendReferencesRequest +"); + + LocationContainer locations = await LanguageClient.SendRequest( + "textDocument/references", + new ReferenceParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + }, + Position = new Position + { + Line = 5, + Character = 0 + }, + Context = new ReferenceContext + { + IncludeDeclaration = false + } + }); + + Assert.Collection(locations, + location1 => + { + Range range = location1.Range; + Assert.Equal(1, range.Start.Line); + Assert.Equal(9, range.Start.Character); + Assert.Equal(1, range.End.Line); + Assert.Equal(33, range.End.Character); + + }, + location2 => + { + Range range = location2.Range; + Assert.Equal(5, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(5, range.End.Line); + Assert.Equal(24, range.End.Character); + }); + } + + [Fact] + public async Task CanSendDocumentHighlightRequest() + { + string scriptPath = NewTestFile(@" +Write-Host 'Hello!' + +Write-Host 'Goodbye' +"); + + DocumentHighlightContainer documentHighlights = + await LanguageClient.SendRequest( + "textDocument/documentHighlight", + new DocumentHighlightParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + }, + Position = new Position + { + Line = 4, + Character = 1 + } + }); + + Assert.Collection(documentHighlights, + documentHighlight1 => + { + Range range = documentHighlight1.Range; + Assert.Equal(1, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(1, range.End.Line); + Assert.Equal(10, range.End.Character); + + }, + documentHighlight2 => + { + Range range = documentHighlight2.Range; + Assert.Equal(3, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(3, range.End.Line); + Assert.Equal(10, range.End.Character); + }); + } + + [Fact] + public async Task CanSendPowerShellGetPSHostProcessesRequest() + { + var process = new Process(); + process.StartInfo.FileName = PwshExe; + process.StartInfo.ArgumentList.Add("-NoProfile"); + process.StartInfo.ArgumentList.Add("-NoLogo"); + process.StartInfo.ArgumentList.Add("-NoExit"); + + process.StartInfo.CreateNoWindow = true; + process.StartInfo.UseShellExecute = false; + + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + + process.Start(); + + // Wait for the process to start. + Thread.Sleep(1000); + + PSHostProcessResponse[] pSHostProcessResponses = null; + + try + { + pSHostProcessResponses = + await LanguageClient.SendRequest( + "powerShell/getPSHostProcesses", + new GetPSHostProcesssesParams { }); + } + finally + { + process.Kill(); + process.Dispose(); + } + + Assert.NotEmpty(pSHostProcessResponses); + } + + [Fact] + public async Task CanSendPowerShellGetRunspaceRequest() + { + var process = new Process(); + process.StartInfo.FileName = PwshExe; + process.StartInfo.ArgumentList.Add("-NoProfile"); + process.StartInfo.ArgumentList.Add("-NoLogo"); + process.StartInfo.ArgumentList.Add("-NoExit"); + + process.StartInfo.CreateNoWindow = true; + process.StartInfo.UseShellExecute = false; + + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + + process.Start(); + + // Wait for the process to start. + Thread.Sleep(1000); + + RunspaceResponse[] runspaceResponses = null; + try + { + runspaceResponses = + await LanguageClient.SendRequest( + "powerShell/getRunspace", + new GetRunspaceParams + { + ProcessId = $"{process.Id}" + }); + } + finally + { + process.Kill(); + process.Dispose(); + } + + Assert.NotEmpty(runspaceResponses); + } + + [Fact] + public async Task CanSendPesterCodeLensRequest() + { + string filePath = NewTestFile(@" +Describe 'DescribeName' { + Context 'ContextName' { + It 'ItName' { + 1 | Should - Be 1 + } + } +} +", isPester: true); + + CodeLensContainer codeLenses = await LanguageClient.SendRequest( + "textDocument/codeLens", + new CodeLensParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + } + }); + + Assert.Collection(codeLenses, + codeLens1 => + { + Range range = codeLens1.Range; + + Assert.Equal(1, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(7, range.End.Line); + Assert.Equal(1, range.End.Character); + + Assert.Equal("Run tests", codeLens1.Command.Title); + }, + codeLens2 => + { + Range range = codeLens2.Range; + + Assert.Equal(1, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(7, range.End.Line); + Assert.Equal(1, range.End.Character); + + Assert.Equal("Debug tests", codeLens2.Command.Title); + }); + } + + [Fact] + public async Task CanSendReferencesCodeLensRequest() + { + string filePath = NewTestFile(@" +function CanSendReferencesCodeLensRequest { + +} + +CanSendReferencesCodeLensRequest +"); + + CodeLensContainer codeLenses = await LanguageClient.SendRequest( + "textDocument/codeLens", + new CodeLensParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + } + }); + + CodeLens codeLens = Assert.Single(codeLenses); + + Range range = codeLens.Range; + Assert.Equal(1, range.Start.Line); + Assert.Equal(0, range.Start.Character); + Assert.Equal(3, range.End.Line); + Assert.Equal(1, range.End.Character); + + CodeLens codeLensResolveResult = await LanguageClient.SendRequest( + "codeLens/resolve", + codeLens); + + Assert.Equal("1 reference", codeLensResolveResult.Command.Title); + } + + [Fact] + public async Task CanSendCodeActionRequest() + { + string filePath = NewTestFile("gci"); + await WaitForDiagnostics(); + + CommandOrCodeActionContainer commandOrCodeActions = + await LanguageClient.SendRequest( + "textDocument/codeAction", + new CodeActionParams + { + TextDocument = new TextDocumentIdentifier( + new Uri(filePath, UriKind.Absolute)), + Range = new Range + { + Start = new Position + { + Line = 0, + Character = 0 + }, + End = new Position + { + Line = 0, + Character = 3 + } + }, + Context = new CodeActionContext + { + Diagnostics = new Container(Diagnostics) + } + }); + + Assert.Single(commandOrCodeActions, + command => command.Command.Name == "PowerShell.ApplyCodeActionEdits"); + } + + [Fact] + public async Task CanSendCompletionAndCompletionResolveRequest() + { + string filePath = NewTestFile("Write-H"); + + CompletionList completionItems = await LanguageClient.TextDocument.Completions( + filePath, line: 0, column: 7); + + CompletionItem completionItem = Assert.Single(completionItems, + completionItem1 => completionItem1.Label == "Write-Host"); + + CompletionItem updatedCompletionItem = await LanguageClient.SendRequest( + "completionItem/resolve", + completionItem); + + Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String); + } + + [Fact] + public async Task CanSendHoverRequest() + { + string filePath = NewTestFile("Write-Host"); + + Hover hover = await LanguageClient.TextDocument.Hover(filePath, line: 0, column: 1); + + Assert.True(hover.Contents.HasMarkedStrings); + Assert.Collection(hover.Contents.MarkedStrings, + str1 => + { + Assert.Equal("function Write-Host", str1.Value); + }, + str2 => + { + Assert.Equal("markdown", str2.Language); + Assert.Equal("Writes customized output to a host.", str2.Value); + }); + } + + [Fact] + public async Task CanSendSignatureHelpRequest() + { + string filePath = NewTestFile("Get-Date "); + + SignatureHelp signatureHelp = await LanguageClient.SendRequest( + "textDocument/signatureHelp", + new SignatureHelpParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + }, + Position = new Position + { + Line = 0, + Character = 9 + } + }); + + Assert.Contains("Get-Date", signatureHelp.Signatures.First().Label); + } + + [Fact] + public async Task CanSendDefinitionRequest() + { + string scriptPath = NewTestFile(@" +function CanSendDefinitionRequest { + +} + +CanSendDefinitionRequest +"); + + LocationOrLocationLinks locationOrLocationLinks = + await LanguageClient.SendRequest( + "textDocument/definition", + new DefinitionParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(scriptPath) + }, + Position = new Position + { + Line = 5, + Character = 2 + } + }); + + LocationOrLocationLink locationOrLocationLink = + Assert.Single(locationOrLocationLinks); + + Assert.Equal(1, locationOrLocationLink.Location.Range.Start.Line); + Assert.Equal(9, locationOrLocationLink.Location.Range.Start.Character); + Assert.Equal(1, locationOrLocationLink.Location.Range.End.Line); + Assert.Equal(33, locationOrLocationLink.Location.Range.End.Character); + } + + [Fact] + public async Task CanSendGetProjectTemplatesRequest() + { + GetProjectTemplatesResponse getProjectTemplatesResponse = + await LanguageClient.SendRequest( + "powerShell/getProjectTemplates", + new GetProjectTemplatesRequest + { + IncludeInstalledModules = true + }); + + Assert.Collection(getProjectTemplatesResponse.Templates.OrderBy(t => t.Title), + template1 => + { + Assert.Equal("AddPSScriptAnalyzerSettings", template1.Title); + }, + template2 => + { + Assert.Equal("New PowerShell Manifest Module", template2.Title); + }); + } + + [Fact] + public async Task CanSendGetCommentHelpRequest() + { + string scriptPath = NewTestFile(@" +function CanSendGetCommentHelpRequest { + param( + [string] + $myParam + ) +} +"); + + CommentHelpRequestResult commentHelpRequestResult = + await LanguageClient.SendRequest( + "powerShell/getCommentHelp", + new CommentHelpRequestParams + { + DocumentUri = new Uri(scriptPath).ToString(), + BlockComment = false, + TriggerPosition = new Position + { + Line = 0, + Character = 0 + } + }); + + Assert.NotEmpty(commentHelpRequestResult.Content); + Assert.Contains("myParam", commentHelpRequestResult.Content[7]); + } + + [Fact] + public async Task CanSendEvaluateRequest() + { + EvaluateResponseBody evaluateResponseBody = + await LanguageClient.SendRequest( + "evaluate", + new EvaluateRequestArguments + { + Expression = "Get-ChildItem" + }); + + // These always gets returned so this test really just makes sure we get _any_ response. + Assert.Equal("", evaluateResponseBody.Result); + Assert.Equal(0, evaluateResponseBody.VariablesReference); + } + + [Fact] + public async Task CanSendGetCommandRequest() + { + List pSCommandMessages = + await LanguageClient.SendRequest>("powerShell/getCommand", new GetCommandParams()); + + Assert.NotEmpty(pSCommandMessages); + // There should be at least 20 commands or so. + Assert.True(pSCommandMessages.Count > 20); + } + + [Fact] + public async Task CanSendExpandAliasRequest() + { + ExpandAliasResult expandAliasResult = + await LanguageClient.SendRequest( + "powerShell/expandAlias", + new ExpandAliasParams + { + Text = "gci" + } + ); + + Assert.Equal("Get-ChildItem", expandAliasResult.Text); + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj new file mode 100644 index 000000000..f2e1e0732 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/test/PowerShellEditorServices.Test.E2E/TestsFixture.cs b/test/PowerShellEditorServices.Test.E2E/TestsFixture.cs new file mode 100644 index 000000000..7b51226f3 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/TestsFixture.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Client.Processes; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Xunit; + +namespace PowerShellEditorServices.Test.E2E +{ + public abstract class TestsFixture : IAsyncLifetime + { + protected readonly static string s_binDir = + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + private readonly static string s_bundledModulePath = new FileInfo(Path.Combine( + s_binDir, + "..", "..", "..", "..", "..", + "module")).FullName; + + private readonly static string s_sessionDetailsPath = Path.Combine( + s_binDir, + $"pses_test_sessiondetails_{Path.GetRandomFileName()}"); + + private readonly static string s_logPath = Path.Combine( + Environment.GetEnvironmentVariable("BUILD_ARTIFACTSTAGINGDIRECTORY") ?? s_binDir, + $"pses_test_logs_{Path.GetRandomFileName()}"); + + const string s_logLevel = "Diagnostic"; + readonly static string[] s_featureFlags = { "PSReadLine" }; + const string s_hostName = "TestHost"; + const string s_hostProfileId = "TestHost"; + const string s_hostVersion = "1.0.0"; + readonly static string[] s_additionalModules = { "PowerShellEditorServices.VSCode" }; + + protected StdioServerProcess _psesProcess; + + public static string PwshExe { get; } = Environment.GetEnvironmentVariable("PWSH_EXE_NAME") ?? "pwsh"; + + public virtual bool IsDebugAdapterTests { get; set; } + + public async Task InitializeAsync() + { + var factory = new LoggerFactory(); + + ProcessStartInfo processStartInfo = new ProcessStartInfo + { + FileName = PwshExe + }; + processStartInfo.ArgumentList.Add("-NoLogo"); + processStartInfo.ArgumentList.Add("-NoProfile"); + processStartInfo.ArgumentList.Add("-EncodedCommand"); + + List args = new List + { + Path.Combine(s_bundledModulePath, "PowerShellEditorServices", "Start-EditorServices.ps1"), + "-LogPath", s_logPath, + "-LogLevel", s_logLevel, + "-SessionDetailsPath", s_sessionDetailsPath, + "-FeatureFlags", string.Join(',', s_featureFlags), + "-HostName", s_hostName, + "-HostProfileId", s_hostProfileId, + "-HostVersion", s_hostVersion, + "-AdditionalModules", string.Join(',', s_additionalModules), + "-BundledModulesPath", s_bundledModulePath, + "-Stdio" + }; + + if (IsDebugAdapterTests) + { + args.Add("-DebugServiceOnly"); + } + + string base64Str = Convert.ToBase64String( + System.Text.Encoding.Unicode.GetBytes(string.Join(' ', args))); + + processStartInfo.ArgumentList.Add(base64Str); + + _psesProcess = new StdioServerProcess(factory, processStartInfo); + await _psesProcess.Start(); + + await CustomInitializeAsync(factory, _psesProcess); + } + + public virtual async Task DisposeAsync() + { + await _psesProcess.Stop(); + } + + public abstract Task CustomInitializeAsync( + ILoggerFactory factory, + StdioServerProcess process); + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json new file mode 100644 index 000000000..79d1ad980 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "parallelizeTestCollections": false +} + diff --git a/tools/PsesPsClient/Client.cs b/tools/PsesPsClient/Client.cs deleted file mode 100644 index bae8c7247..000000000 --- a/tools/PsesPsClient/Client.cs +++ /dev/null @@ -1,581 +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 Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers; -using System.Text; -using System.IO; -using Newtonsoft.Json.Linq; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using System.Collections.Generic; -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using System.Threading; -using System.Linq; - -namespace PsesPsClient -{ - /// - /// A Language Server Protocol named pipe connection. - /// - public class PsesLspClient : IDisposable - { - /// - /// Create a new LSP pipe around a given named pipe. - /// - /// The name of the named pipe to use. - /// A new LspPipe instance around the given named pipe. - public static PsesLspClient Create(string pipeName) - { - var pipeClient = new NamedPipeClientStream( - pipeName: pipeName, - serverName: ".", - direction: PipeDirection.InOut, - options: PipeOptions.Asynchronous); - - return new PsesLspClient(pipeClient); - } - - private readonly NamedPipeClientStream _namedPipeClient; - - private readonly JsonSerializerSettings _jsonSettings; - - private readonly JsonSerializer _jsonSerializer; - - private readonly JsonRpcMessageSerializer _jsonRpcSerializer; - - private readonly Encoding _pipeEncoding; - - private int _msgId; - - private StreamWriter _writer; - - private MessageStreamListener _listener; - - /// - /// Create a new LSP pipe around a named pipe client stream. - /// - /// The named pipe client stream to use for the LSP pipe. - public PsesLspClient(NamedPipeClientStream namedPipeClient) - { - _namedPipeClient = namedPipeClient; - - _jsonSettings = new JsonSerializerSettings() - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - - _jsonSerializer = JsonSerializer.Create(_jsonSettings); - - // Reuse the PSES JSON RPC serializer - _jsonRpcSerializer = new JsonRpcMessageSerializer(); - - _pipeEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - } - - /// - /// Connect to the named pipe server. - /// - public void Connect() - { - _namedPipeClient.Connect(timeout: 1000); - _listener = new MessageStreamListener(new StreamReader(_namedPipeClient, _pipeEncoding)); - _writer = new StreamWriter(_namedPipeClient, _pipeEncoding) - { - AutoFlush = true - }; - - _listener.Start(); - } - - /// - /// Write a request to the LSP pipe. - /// - /// The method of the request. - /// The parameters of the request. May be null. - /// A representation of the request sent. - public LspRequest WriteRequest( - string method, - object parameters) - { - _msgId++; - - Message msg = Message.Request( - _msgId.ToString(), - method, - parameters != null ? JToken.FromObject(parameters, _jsonSerializer) : JValue.CreateNull()); - - JObject msgJson = _jsonRpcSerializer.SerializeMessage(msg); - string msgString = JsonConvert.SerializeObject(msgJson, _jsonSettings); - byte[] msgBytes = _pipeEncoding.GetBytes(msgString); - - string header = "Content-Length: " + msgBytes.Length + "\r\n\r\n"; - - _writer.Write(header + msgString); - _writer.Flush(); - - return new LspRequest(msg.Id, method, msgJson["params"]); - } - - /// - /// Get all the pending notifications from the server. - /// - /// Any pending notifications from the server. - public IEnumerable GetNotifications() - { - return _listener.DrainNotifications(); - } - - /// - /// Get all the pending requests from the server. - /// - /// Any pending requests from the server. - public IEnumerable GetRequests() - { - return _listener.DrainRequests(); - } - - /// - /// Get the next response from the server, if one is available within the given time. - /// - /// The next response from the server. - /// How long to wait for a response. - /// True if there is a next response, false if it timed out. - public bool TryGetResponse(string id, out LspResponse response, int millisTimeout) - { - return _listener.TryGetResponse(id, out response, millisTimeout); - } - - /// - /// Dispose of the pipe. This will also close the pipe. - /// - public void Dispose() - { - _writer.Dispose(); - _listener.Dispose(); - _namedPipeClient.Close(); - _namedPipeClient.Dispose(); - } - } - - /// - /// A dedicated listener to run a thread for receiving pipe messages, - /// so the the pipe is not blocked. - /// - public class MessageStreamListener : IDisposable - { - private readonly StreamReader _stream; - - private readonly StringBuilder _headerBuffer; - - private readonly ConcurrentQueue _requestQueue; - - private readonly ConcurrentQueue _notificationQueue; - - private readonly ConcurrentDictionary _responses; - - private readonly CancellationTokenSource _cancellationSource; - - private readonly BlockingCollection _responseReceivedChannel; - - private char[] _readerBuffer; - - /// - /// Create a listener around a stream. - /// - /// The stream to listen for messages on. - public MessageStreamListener(StreamReader stream) - { - _stream = stream; - _readerBuffer = new char[1024]; - _headerBuffer = new StringBuilder(128); - _notificationQueue = new ConcurrentQueue(); - _requestQueue = new ConcurrentQueue(); - _responses = new ConcurrentDictionary(); - _cancellationSource = new CancellationTokenSource(); - _responseReceivedChannel = new BlockingCollection(); - } - - /// - /// Get all pending notifications. - /// - public IEnumerable DrainNotifications() - { - return DrainQueue(_notificationQueue); - } - - /// - /// Get all pending requests. - /// - public IEnumerable DrainRequests() - { - return DrainQueue(_requestQueue); - } - - /// - /// Get the next response if there is one, otherwise instantly return false. - /// - /// The first response in the response queue if any, otherwise null. - /// True if there was a response to get, false otherwise. - public bool TryGetResponse(string id, out LspResponse response) - { - _responseReceivedChannel.TryTake(out bool _, millisecondsTimeout: 0); - return _responses.TryRemove(id, out response); - } - - /// - /// Get the next response within the given timeout. - /// - /// The first response in the queue, if any. - /// The maximum number of milliseconds to wait for a response. - /// True if there was a response to get, false otherwise. - public bool TryGetResponse(string id, out LspResponse response, int millisTimeout) - { - if (_responses.TryRemove(id, out response)) - { - return true; - } - - if (_responseReceivedChannel.TryTake(out bool _, millisTimeout)) - { - return _responses.TryRemove(id, out response); - } - - response = null; - return false; - } - - /// - /// Start the pipe listener on its own thread. - /// - public void Start() - { - Task.Run(() => RunListenLoop()); - } - - /// - /// End the pipe listener loop. - /// - public void Stop() - { - _cancellationSource.Cancel(); - } - - /// - /// Stops and disposes the pipe listener. - /// - public void Dispose() - { - Stop(); - _stream.Dispose(); - } - - private async Task RunListenLoop() - { - CancellationToken cancellationToken = _cancellationSource.Token; - while (!cancellationToken.IsCancellationRequested) - { - LspMessage msg; - msg = await ReadMessage().ConfigureAwait(false); - switch (msg) - { - case LspNotification notification: - _notificationQueue.Enqueue(notification); - continue; - - case LspResponse response: - _responses[response.Id] = response; - _responseReceivedChannel.Add(true); - continue; - - case LspRequest request: - _requestQueue.Enqueue(request); - continue; - } - } - } - - private async Task ReadMessage() - { - int contentLength = GetContentLength(); - string msgString = await ReadString(contentLength).ConfigureAwait(false); - JObject msgJson = JObject.Parse(msgString); - - if (msgJson.TryGetValue("method", out JToken methodToken)) - { - string method = ((JValue)methodToken).Value.ToString(); - if (msgJson.TryGetValue("id", out JToken idToken)) - { - string requestId = ((JValue)idToken).Value.ToString(); - return new LspRequest(requestId, method, msgJson["params"]); - } - - return new LspNotification(method, msgJson["params"]); - } - - string id = ((JValue)msgJson["id"]).Value.ToString(); - - if (msgJson.TryGetValue("result", out JToken resultToken)) - { - return new LspSuccessfulResponse(id, resultToken); - } - - JObject errorBody = (JObject)msgJson["error"]; - JsonRpcErrorCode errorCode = (JsonRpcErrorCode)(int)((JValue)errorBody["code"]).Value; - string message = (string)((JValue)errorBody["message"]).Value; - return new LspErrorResponse(id, errorCode, message, errorBody["data"]); - } - - private async Task ReadString(int bytesToRead) - { - if (bytesToRead > _readerBuffer.Length) - { - Array.Resize(ref _readerBuffer, _readerBuffer.Length * 2); - } - - int readLen = await _stream.ReadAsync(_readerBuffer, 0, bytesToRead).ConfigureAwait(false); - - return new string(_readerBuffer, 0, readLen); - } - - private int GetContentLength() - { - _headerBuffer.Clear(); - int endHeaderState = 0; - int currChar; - while ((currChar = _stream.Read()) >= 0) - { - char c = (char)currChar; - _headerBuffer.Append(c); - switch (c) - { - case '\r': - if (endHeaderState == 2) - { - endHeaderState = 3; - continue; - } - - if (endHeaderState == 0) - { - endHeaderState = 1; - continue; - } - - endHeaderState = 0; - continue; - - case '\n': - if (endHeaderState == 1) - { - endHeaderState = 2; - continue; - } - - if (endHeaderState == 3) - { - return ParseContentLength(_headerBuffer.ToString()); - } - - endHeaderState = 0; - continue; - - default: - endHeaderState = 0; - continue; - } - } - - throw new InvalidDataException("Buffer emptied before end of headers"); - } - - private static int ParseContentLength(string headers) - { - const string clHeaderPrefix = "Content-Length: "; - - int clIdx = headers.IndexOf(clHeaderPrefix, StringComparison.Ordinal); - if (clIdx < 0) - { - throw new InvalidDataException("No Content-Length header found"); - } - - int endIdx = headers.IndexOf("\r\n", clIdx, StringComparison.Ordinal); - if (endIdx < 0) - { - throw new InvalidDataException("Header CRLF terminator not found"); - } - - int numStartIdx = clIdx + clHeaderPrefix.Length; - int numLength = endIdx - numStartIdx; - - return int.Parse(headers.Substring(numStartIdx, numLength)); - } - - private static IEnumerable DrainQueue(ConcurrentQueue queue) - { - if (queue.IsEmpty) - { - return Enumerable.Empty(); - } - - var list = new List(); - while (queue.TryDequeue(out TElement element)) - { - list.Add(element); - } - return list; - } - - } - - /// - /// Represents a Language Server Protocol message. - /// - public abstract class LspMessage - { - protected LspMessage() - { - } - } - - /// - /// A Language Server Protocol notifcation or event. - /// - public class LspNotification : LspMessage - { - public LspNotification(string method, JToken parameters) - { - Method = method; - Params = parameters; - } - - /// - /// The notification method. - /// - public string Method { get; } - - /// - /// Any parameters for the notification. - /// - public JToken Params { get; } - } - - /// - /// A Language Server Protocol request. - /// May be a client -> server or a server -> client request. - /// - public class LspRequest : LspMessage - { - public LspRequest(string id, string method, JToken parameters) - { - Id = id; - Method = method; - Params = parameters; - } - - /// - /// The ID of the request. Usually an integer. - /// - public string Id { get; } - - /// - /// The method of the request. - /// - public string Method { get; } - - /// - /// Any parameters of the request. - /// - public JToken Params { get; } - } - - /// - /// A Language Server Protocol response message. - /// - public abstract class LspResponse : LspMessage - { - protected LspResponse(string id) - { - Id = id; - } - - /// - /// The ID of the response. Will match the ID of the request triggering it. - /// - public string Id { get; } - } - - /// - /// A successful Language Server Protocol response message. - /// - public class LspSuccessfulResponse : LspResponse - { - public LspSuccessfulResponse(string id, JToken result) - : base(id) - { - Result = result; - } - - /// - /// The result field of the response. - /// - public JToken Result { get; } - } - - /// - /// A Language Server Protocol error response message. - /// - public class LspErrorResponse : LspResponse - { - public LspErrorResponse( - string id, - JsonRpcErrorCode code, - string message, - JToken data) - : base(id) - { - Code = code; - Message = message; - Data = data; - } - - /// - /// The error code sent by the server, may not correspond to a known enum type. - /// - public JsonRpcErrorCode Code { get; } - - /// - /// The error message. - /// - public string Message { get; } - - /// - /// Extra error data. - /// - public JToken Data { get; } - } - - /// - /// Error codes used by the Language Server Protocol. - /// - public enum JsonRpcErrorCode : int - { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - ServerErrorStart = -32099, - ServerErrorEnd = -32000, - ServerNotInitialized = -32002, - UnknownErrorCode = -32001, - RequestCancelled = -32800, - ContentModified = -32801, - } -} diff --git a/tools/PsesPsClient/PsesPsClient.csproj b/tools/PsesPsClient/PsesPsClient.csproj deleted file mode 100644 index 5cde53561..000000000 --- a/tools/PsesPsClient/PsesPsClient.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netstandard2.0 - - - - - - - - - - - - diff --git a/tools/PsesPsClient/PsesPsClient.psd1 b/tools/PsesPsClient/PsesPsClient.psd1 deleted file mode 100644 index c102d30c8..000000000 --- a/tools/PsesPsClient/PsesPsClient.psd1 +++ /dev/null @@ -1,130 +0,0 @@ -# -# Module manifest for module 'PsesPsClient' -# -# Generated by: Microsoft Corporation -# -# Generated on: 26/4/19 -# - -@{ - -# Script module or binary module file associated with this manifest. -RootModule = 'PsesPsClient.psm1' - -# Version number of this module. -ModuleVersion = '0.0.1' - -# Supported PSEditions -CompatiblePSEditions = 'Core', 'Desktop' - -# ID used to uniquely identify this module -GUID = 'ce491ff9-3eab-443c-b3a2-cc412ddeef65' - -# Author of this module -Author = 'Microsoft Corporation' - -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' - -# Copyright statement for this module -Copyright = '(c) Microsoft Corporation' - -# Description of the functionality provided by this module -# Description = '' - -# Minimum version of the PowerShell engine required by this module -PowerShellVersion = '5.1' - -# Name of the PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -NestedModules = @('PsesPsClient.dll') - -# 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 = @( - 'Start-PsesServer', - 'Connect-PsesServer', - 'Send-LspRequest', - 'Send-LspInitializeRequest', - 'Send-LspShutdownRequest', - 'Get-LspResponse' -) - -# 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 = '*' - -# Variables to export from this module -VariablesToExport = '*' - -# Aliases 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 aliases to export. -AliasesToExport = '*' - -# DSC resources to export from this module -# DscResourcesToExport = @() - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - # Tags = @() - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - # ProjectUri = '' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - } # End of PSData hashtable - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - -} - diff --git a/tools/PsesPsClient/PsesPsClient.psm1 b/tools/PsesPsClient/PsesPsClient.psm1 deleted file mode 100644 index a66e5c7d1..000000000 --- a/tools/PsesPsClient/PsesPsClient.psm1 +++ /dev/null @@ -1,375 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -$script:PsesBundledModulesDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( - "$PSScriptRoot/../../../../module") - -class PsesStartupOptions -{ - [string] $LogPath - [string] $LogLevel - [string] $SessionDetailsPath - [string[]] $FeatureFlags - [string] $HostName - [string] $HostProfileId - [version] $HostVersion - [string[]] $AdditionalModules - [string] $BundledModulesPath - [bool] $EnableConsoleRepl -} - -class PsesServerInfo -{ - [pscustomobject]$SessionDetails - [System.Diagnostics.Process]$PsesProcess - [PsesStartupOptions]$StartupOptions - [string]$LogPath -} - -function Start-PsesServer -{ - [CmdletBinding(SupportsShouldProcess)] - [OutputType([PsesServerInfo])] - param( - [Parameter()] - [ValidateNotNullOrEmpty()] - [string] - $EditorServicesPath = "$script:PsesBundledModulesDir/PowerShellEditorServices/Start-EditorServices.ps1", - - [Parameter()] - [ValidateNotNullOrEmpty()] - [string] - $LogPath, - - [Parameter()] - [ValidateSet("Diagnostic", "Normal", "Verbose", "Error")] - [string] - $LogLevel = 'Diagnostic', - - [Parameter()] - [ValidateNotNullOrEmpty()] - [string] - $SessionDetailsPath, - - [Parameter()] - [ValidateNotNull()] - [string[]] - $FeatureFlags = @('PSReadLine'), - - [Parameter()] - [ValidateNotNullOrEmpty()] - [string] - $HostName = 'PSES Test Host', - - [Parameter()] - [ValidateNotNullOrEmpty()] - [string] - $HostProfileId = 'TestHost', - - [Parameter()] - [ValidateNotNull()] - [version] - $HostVersion = '1.99', - - [Parameter()] - [ValidateNotNull()] - [string[]] - $AdditionalModules = @('PowerShellEditorServices.VSCode'), - - [Parameter()] - [ValidateNotNullOrEmpty()] - [string] - $BundledModulesPath, - - [Parameter()] - [switch] - $EnableConsoleRepl, - - [Parameter()] - [string] - $ErrorFile - ) - - $EditorServicesPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($EditorServicesPath) - - $instanceId = Get-RandomHexString - - $tempDir = [System.IO.Path]::GetTempPath() - - if (-not $LogPath) - { - $LogPath = Join-Path $tempDir "pseslogs_$instanceId.log" - } - - if (-not $SessionDetailsPath) - { - $SessionDetailsPath = Join-Path $tempDir "psessession_$instanceId.log" - } - - if (-not $BundledModulesPath) - { - $BundledModulesPath = $script:PsesBundledModulesDir - } - - $editorServicesOptions = @{ - LogPath = $LogPath - LogLevel = $LogLevel - SessionDetailsPath = $SessionDetailsPath - FeatureFlags = $FeatureFlags - HostName = $HostName - HostProfileId = $HostProfileId - HostVersion = $HostVersion - AdditionalModules = $AdditionalModules - BundledModulesPath = $BundledModulesPath - EnableConsoleRepl = $EnableConsoleRepl - } - - $startPsesCommand = Unsplat -Prefix "& '$EditorServicesPath'" -SplatParams $editorServicesOptions - - $pwshPath = (Get-Process -Id $PID).Path - - if (-not $PSCmdlet.ShouldProcess("& '$pwshPath' -Command '$startPsesCommand'")) - { - return - } - - $startArgs = @( - '-NoLogo', - '-NoProfile', - '-NoExit', - '-Command', - $startPsesCommand - ) - - $startProcParams = @{ - PassThru = $true - FilePath = $pwshPath - ArgumentList = $startArgs - } - - if ($ErrorFile) - { - $startProcParams.RedirectStandardError = $ErrorFile - } - - $serverProcess = Start-Process @startProcParams - - $sessionPath = $editorServicesOptions.SessionDetailsPath - - $i = 0 - while (-not (Test-Path $sessionPath)) - { - if ($i -ge 10) - { - throw "No session file found - server failed to start" - } - - Start-Sleep 1 - $null = $i++ - } - - return [PsesServerInfo]@{ - PsesProcess = $serverProcess - SessionDetails = Get-Content -Raw $editorServicesOptions.SessionDetailsPath | ConvertFrom-Json - StartupOptions = $editorServicesOptions - LogPath = $LogPath - } -} - -function Connect-PsesServer -{ - [OutputType([PsesPsClient.PsesLspClient])] - param( - [Parameter(Mandatory)] - [string] - $PipeName - ) - - $psesIdx = $PipeName.IndexOf('PSES') - if ($psesIdx -gt 0) - { - $PipeName = $PipeName.Substring($psesIdx) - } - - $client = [PsesPsClient.PsesLspClient]::Create($PipeName) - $client.Connect() - return $client -} - -function Send-LspInitializeRequest -{ - [OutputType([PsesPsClient.LspRequest])] - param( - [Parameter(Position = 0, Mandatory)] - [PsesPsClient.PsesLspClient] - $Client, - - [Parameter()] - [int] - $ProcessId = $PID, - - [Parameter()] - [string] - $RootPath = (Get-Location), - - [Parameter()] - [string] - $RootUri, - - [Parameter()] - [Microsoft.PowerShell.EditorServices.Protocol.LanguageServer.ClientCapabilities] - $ClientCapabilities = ([Microsoft.PowerShell.EditorServices.Protocol.LanguageServer.ClientCapabilities]::new()), - - [Parameter()] - [hashtable] - $InitializeOptions = $null - ) - - $parameters = [Microsoft.PowerShell.EditorServices.Protocol.LanguageServer.InitializeParams]@{ - ProcessId = $ProcessId - Capabilities = $ClientCapabilities - InitializeOptions = $InitializeOptions - } - - if ($RootUri) - { - $parameters.RootUri = $RootUri - } - else - { - $parameters.RootPath = $RootPath - } - - return Send-LspRequest -Client $Client -Method 'initialize' -Parameters $parameters -} - -function Send-LspShutdownRequest -{ - [OutputType([PsesPsClient.LspRequest])] - param( - [Parameter(Position = 0, Mandatory)] - [PsesPsClient.PsesLspClient] - $Client - ) - - return Send-LspRequest -Client $Client -Method 'shutdown' -} - -function Send-LspRequest -{ - [OutputType([PsesPsClient.LspRequest])] - param( - [Parameter(Position = 0, Mandatory)] - [PsesPsClient.PsesLspClient] - $Client, - - [Parameter(Position = 1, Mandatory)] - [string] - $Method, - - [Parameter(Position = 2)] - $Parameters = $null - ) - - return $Client.WriteRequest($Method, $Parameters) -} - -function Get-LspResponse -{ - [OutputType([PsesPsClient.LspResponse])] - param( - [Parameter(Position = 0, Mandatory)] - [PsesPsClient.PsesLspClient] - $Client, - - [Parameter(Position = 1, Mandatory)] - [string] - $Id, - - [Parameter()] - [int] - $WaitMillis = 5000 - ) - - $lspResponse = $null - - if ($Client.TryGetResponse($Id, [ref]$lspResponse, $WaitMillis)) - { - return $lspResponse - } -} - -function Unsplat -{ - param( - [string]$Prefix, - [hashtable]$SplatParams) - - $sb = New-Object 'System.Text.StringBuilder' ($Prefix) - - foreach ($key in $SplatParams.get_Keys()) - { - $val = $SplatParams[$key] - - if (-not $val) - { - continue - } - - $null = $sb.Append(" -$key") - - if ($val -is [switch]) - { - continue - } - - if ($val -is [array]) - { - $null = $sb.Append(' @(') - for ($i = 0; $i -lt $val.Count; $i++) - { - $null = $sb.Append("'").Append($val[$i]).Append("'") - if ($i -lt $val.Count - 1) - { - $null = $sb.Append(',') - } - } - $null = $sb.Append(')') - continue - } - - if ($val -is [version]) - { - $val = [string]$val - } - - if ($val -is [string]) - { - $null = $sb.Append(" '$val'") - continue - } - - throw "Bad value '$val' of type $($val.GetType())" - } - - return $sb.ToString() -} - -$script:Random = [System.Random]::new() -function Get-RandomHexString -{ - param([int]$Length = 10) - - $buffer = [byte[]]::new($Length / 2) - $script:Random.NextBytes($buffer) - $str = ($buffer | ForEach-Object { "{0:x02}" -f $_ }) -join '' - - if ($Length % 2 -ne 0) - { - $str += ($script:Random.Next() | ForEach-Object { "{0:02}" -f $_ }) - } - - return $str -} diff --git a/tools/PsesPsClient/build.ps1 b/tools/PsesPsClient/build.ps1 deleted file mode 100644 index a9da9a778..000000000 --- a/tools/PsesPsClient/build.ps1 +++ /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. -# - -param( - [Parameter()] - [string] - $DotnetExe = 'dotnet' -) - -$ErrorActionPreference = 'Stop' - -$script:OutDir = "$PSScriptRoot/out" -$script:OutModDir = "$script:OutDir/PsesPsClient" - -$script:ModuleComponents = @{ - "bin/Debug/netstandard2.0/publish/PsesPsClient.dll" = "PsesPsClient.dll" - "bin/Debug/netstandard2.0/publish/Newtonsoft.Json.dll" = "Newtonsoft.Json.dll" - "PsesPsClient.psm1" = "PsesPsClient.psm1" - "PsesPsClient.psd1" = "PsesPsClient.psd1" -} - -$binDir = "$PSScriptRoot/bin" -$objDir = "$PSScriptRoot/obj" -foreach ($dir in $binDir,$objDir,$script:OutDir) -{ - if (Test-Path $dir) - { - Remove-Item -Force -Recurse $dir - } -} - -Push-Location $PSScriptRoot -try -{ - & $DotnetExe publish --framework 'netstandard2.0' - - New-Item -Path $script:OutModDir -ItemType Directory - foreach ($key in $script:ModuleComponents.get_Keys()) - { - $val = $script:ModuleComponents[$key] - Copy-Item -Path "$PSScriptRoot/$key" -Destination "$script:OutModDir/$val" - } -} -finally -{ - Pop-Location -}