diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json index a8ae267f03..44c83732fa 100644 --- a/.config/tsaoptions.json +++ b/.config/tsaoptions.json @@ -2,7 +2,7 @@ "instanceUrl": "https://msazure.visualstudio.com", "projectName": "One", "areaPath": "One\\MGMT\\Compute\\Powershell\\Powershell", - "notificationAliases": [ "andschwa@microsoft.com", "slee@microsoft.com" ], + "notificationAliases": ["andschwa@microsoft.com", "slee@microsoft.com"], "codebaseName": "PowerShell_vscode-powershell_20240328", - "tools": [ "CredScan", "PoliCheck", "BinSkim" ] + "tools": ["CredScan", "PoliCheck", "BinSkim"] } diff --git a/.gitattributes b/.gitattributes index 2a922ee8df..e6caee9a0c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,8 +3,3 @@ # Set svg to binary type, as SVG is unlikely to be editted by hand. Can be treated as checked in blob *.svg binary - -# .gitattributes in project root -# npm now seems to be insisting on LF - see https://github.com/npm/npm/issues/17161 -package.json text eol=lf -package-lock.json text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 42d40f611f..d90459770a 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -2,98 +2,98 @@ name: 🐛 Bug report description: Open an issue about a bug that needs fixing. labels: ["Issue-Bug", "Needs: Triage"] body: -- type: checkboxes - attributes: - label: Prerequisites - options: - - label: I have written a descriptive issue title. + - type: checkboxes + attributes: + label: Prerequisites + options: + - label: I have written a descriptive issue title. + required: true + - label: I have searched all [_open and closed_ issues](https://github.com/PowerShell/vscode-powershell/issues?q=is%3Aissue) to ensure it has not already been reported. + - label: I have read the [troubleshooting](https://github.com/PowerShell/vscode-powershell/blob/main/docs/troubleshooting.md) guide. + - label: I am sure this issue is with the _extension itself_ and does not reproduce in a standalone [PowerShell](https://github.com/PowerShell/PowerShell/issues/new/choose) instance. + - label: I have verified that I am using the latest version of Visual Studio Code and the PowerShell extension. + - label: If this is a security issue, I have read the [security issue reporting guidance](https://github.com/PowerShell/vscode-powershell/blob/main/SECURITY.md). + - type: textarea + id: summary + attributes: + label: Summary + description: Explain the problem briefly below. + placeholder: I am experiencing a problem with X. I think Y should be happening but Z is actually happening. + validations: required: true - - label: I have searched all [_open and closed_ issues](https://github.com/PowerShell/vscode-powershell/issues?q=is%3Aissue) to ensure it has not already been reported. - - label: I have read the [troubleshooting](https://github.com/PowerShell/vscode-powershell/blob/main/docs/troubleshooting.md) guide. - - label: I am sure this issue is with the _extension itself_ and does not reproduce in a standalone [PowerShell](https://github.com/PowerShell/PowerShell/issues/new/choose) instance. - - label: I have verified that I am using the latest version of Visual Studio Code and the PowerShell extension. - - label: If this is a security issue, I have read the [security issue reporting guidance](https://github.com/PowerShell/vscode-powershell/blob/main/SECURITY.md). -- type: textarea - id: summary - attributes: - label: Summary - description: Explain the problem briefly below. - placeholder: I am experiencing a problem with X. I think Y should be happening but Z is actually happening. - validations: - required: true -- type: textarea - id: powershell-version - attributes: - label: PowerShell Version - description: Paste verbatim output from `$PSVersionTable; $Host` below. **Please include `$Host`** so we know this version is from the Extension Terminal! - render: console - placeholder: | - PS> $PSVersionTable; $Host + - type: textarea + id: powershell-version + attributes: + label: PowerShell Version + description: Paste verbatim output from `$PSVersionTable; $Host` below. **Please include `$Host`** so we know this version is from the Extension Terminal! + render: console + placeholder: | + PS> $PSVersionTable; $Host - Name Value - ---- ----- - PSVersion 7.4.0 - PSEdition Core - GitCommitId 7.4.0 - OS Microsoft Windows 10.0.22631 - Platform Win32NT - PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} - PSRemotingProtocolVersion 2.3 - SerializationVersion 1.1.0.1 - WSManStackVersion 3.0 + Name Value + ---- ----- + PSVersion 7.4.0 + PSEdition Core + GitCommitId 7.4.0 + OS Microsoft Windows 10.0.22631 + Platform Win32NT + PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} + PSRemotingProtocolVersion 2.3 + SerializationVersion 1.1.0.1 + WSManStackVersion 3.0 - Name : Visual Studio Code Host - Version : 2023.11.0 - InstanceId : 803ce61b-6187-4574-9c1f-827ebb11b8b6 - UI : System.Management.Automation.Internal.Host.InternalHostUserInterface - CurrentCulture : en-US - CurrentUICulture : en-US - PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy - DebuggerEnabled : True - IsRunspacePushed : False - Runspace : System.Management.Automation.Runspaces.LocalRunspace - validations: - required: true -- type: textarea - id: vscode-version - attributes: - label: Visual Studio Code Version - description: Paste verbatim output from `code --version` below. - render: console - placeholder: | - PS> code --version + Name : Visual Studio Code Host + Version : 2023.11.0 + InstanceId : 803ce61b-6187-4574-9c1f-827ebb11b8b6 + UI : System.Management.Automation.Internal.Host.InternalHostUserInterface + CurrentCulture : en-US + CurrentUICulture : en-US + PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy + DebuggerEnabled : True + IsRunspacePushed : False + Runspace : System.Management.Automation.Runspaces.LocalRunspace + validations: + required: true + - type: textarea + id: vscode-version + attributes: + label: Visual Studio Code Version + description: Paste verbatim output from `code --version` below. + render: console + placeholder: | + PS> code --version - 1.57.1 - 507ce72a4466fbb27b715c3722558bb15afa9f48 - arm64 - validations: - required: true -- type: textarea - id: extension-version - attributes: - label: Extension Version - description: Paste verbatim output from `code --list-extensions --show-versions | Select-String powershell` below. - render: console - placeholder: | - PS> code --list-extensions --show-versions | Select-String powershell + 1.57.1 + 507ce72a4466fbb27b715c3722558bb15afa9f48 + arm64 + validations: + required: true + - type: textarea + id: extension-version + attributes: + label: Extension Version + description: Paste verbatim output from `code --list-extensions --show-versions | Select-String powershell` below. + render: console + placeholder: | + PS> code --list-extensions --show-versions | Select-String powershell - ms-vscode.powershell@2021.8.0 - validations: - required: true -- type: textarea - id: steps-to-reproduce - attributes: - label: Steps to Reproduce - description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. - validations: - required: true -- type: textarea - id: visuals - attributes: - label: Visuals - description: Please upload images or animations that can be used to reproduce issues in the area below. Try the [Steps Recorder](https://support.microsoft.com/en-us/windows/record-steps-to-reproduce-a-problem-46582a9b-620f-2e36-00c9-04e25d784e47) on Windows or [Screenshot](https://support.apple.com/en-us/HT208721) on macOS. -- type: textarea - id: logs - attributes: - label: Logs - description: Please upload logs collected by following these [instructions](https://github.com/PowerShell/vscode-powershell/blob/main/docs/troubleshooting.md#logs) in the area below. Be careful to scrub sensitive information! + ms-vscode.powershell@2021.8.0 + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. + validations: + required: true + - type: textarea + id: visuals + attributes: + label: Visuals + description: Please upload images or animations that can be used to reproduce issues in the area below. Try the [Steps Recorder](https://support.microsoft.com/en-us/windows/record-steps-to-reproduce-a-problem-46582a9b-620f-2e36-00c9-04e25d784e47) on Windows or [Screenshot](https://support.apple.com/en-us/HT208721) on macOS. + - type: textarea + id: logs + attributes: + label: Logs + description: Please upload logs collected by following these [instructions](https://github.com/PowerShell/vscode-powershell/blob/main/docs/troubleshooting.md#logs) in the area below. Be careful to scrub sensitive information! diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index d6b9a6a080..22599f9c7e 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -2,25 +2,25 @@ name: ✨ Feature request description: Open an issue about a potential new feature or improvement. labels: ["Issue-Enhancement", "Needs: Triage"] body: -- type: checkboxes - attributes: - label: Prerequisites - options: - - label: I have written a descriptive issue title. + - type: checkboxes + attributes: + label: Prerequisites + options: + - label: I have written a descriptive issue title. + required: true + - label: I have searched all [issues](https://github.com/PowerShell/vscode-powershell/issues?q=is%3Aissue) to ensure it has not already been reported. + required: true + - type: textarea + id: summary + attributes: + label: Summary + description: Explain the feature request below. + placeholder: I would like to do X because it would be useful for Y and I cannot currently do it with Z. + validations: required: true - - label: I have searched all [issues](https://github.com/PowerShell/vscode-powershell/issues?q=is%3Aissue) to ensure it has not already been reported. - required: true -- type: textarea - id: summary - attributes: - label: Summary - description: Explain the feature request below. - placeholder: I would like to do X because it would be useful for Y and I cannot currently do it with Z. - validations: - required: true -- type: textarea - id: proposed-design - attributes: - label: Proposed Design - description: Optionally explain any technical design below. - placeholder: We could accomplish this by extending X to take Y and yield Z. + - type: textarea + id: proposed-design + attributes: + label: Proposed Design + description: Optionally explain any technical design below. + placeholder: We could accomplish this by extending X to take Y and yield Z. diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 8d416d460a..fbd38d1088 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -19,7 +19,7 @@ jobs: env: DOTNET_NOLOGO: true DOTNET_GENERATE_ASPNET_CERTIFICATE: false - DISPLAY: ':99.0' + DISPLAY: ":99.0" timeout-minutes: 10 steps: - name: Checkout PowerShellEditorServices @@ -70,11 +70,11 @@ jobs: if: always() with: name: vscode-powershell-vsix-${{ matrix.os }} - path: '**/*.vsix' + path: "**/*.vsix" - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: vscode-powershell-test-results-${{ matrix.os }} - path: '**/test-results.xml' + path: "**/test-results.xml" diff --git a/.markdownlint.json b/.markdownlint.json index e1a57605cd..7f90a5dce8 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,6 +1,6 @@ { - "MD013": false, - "MD033": false, - "MD022": false, - "MD024": false + "MD013": false, + "MD033": false, + "MD022": false, + "MD024": false } diff --git a/.pipelines/vscode-powershell-Official.yml b/.pipelines/vscode-powershell-Official.yml index 018e598c0e..69d83c9533 100644 --- a/.pipelines/vscode-powershell-Official.yml +++ b/.pipelines/vscode-powershell-Official.yml @@ -9,21 +9,21 @@ ################################################################################# trigger: -- main + - main schedules: -- cron: '25 9 * * 3' - displayName: Weekly CodeQL - branches: - include: - - main - always: true + - cron: "25 9 * * 3" + displayName: Weekly CodeQL + branches: + include: + - main + always: true parameters: -- name: debug - displayName: Enable debug output - type: boolean - default: false + - name: debug + displayName: Enable debug output + type: boolean + default: false variables: system.debug: ${{ parameters.debug }} @@ -37,13 +37,13 @@ resources: name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main pipelines: - - pipeline: PowerShellEditorServices-Official - source: PowerShellEditorServices-Official - trigger: - branches: - - main - stages: - - release + - pipeline: PowerShellEditorServices-Official + source: PowerShellEditorServices-Official + trigger: + branches: + - main + stages: + - release extends: # https://aka.ms/obpipelines/templates @@ -59,198 +59,198 @@ extends: Version: 2022 Network: KS3 stages: - - stage: build - jobs: - - job: main - displayName: Build package - pool: - type: windows + - stage: build + jobs: + - job: main + displayName: Build package + pool: + type: windows + variables: + ob_outputDirectory: $(Build.SourcesDirectory)/out + ob_sdl_codeSignValidation_excludes: -|**\*.js # Node.js JavaScript signatures are not supported + steps: + - pwsh: | + $version = (Get-Content -Raw -Path package.json | ConvertFrom-Json).version + Write-Output "##vso[task.setvariable variable=vsixVersion;isOutput=true]$version" + $prerelease = ([semver]$version).Minor % 2 -ne 0 + if ($prerelease) { $version += "-preview" } + Write-Output "##vso[task.setvariable variable=version;isOutput=true]$version" + Write-Output "##vso[task.setvariable variable=prerelease;isOutput=true]$prerelease" + name: package + displayName: Get version from package.json + - task: onebranch.pipeline.version@1 + displayName: Set OneBranch version + inputs: + system: Custom + customVersion: $(package.version) + - task: UseNode@1 + displayName: Use Node 20.x + inputs: + version: 20.x + - task: DownloadPipelineArtifact@2 + displayName: Download PowerShellEditorServices + inputs: + source: specific + project: PowerShellCore + definition: 2905 + specificBuildWithTriggering: true + allowPartiallySucceededBuilds: true + buildVersionToDownload: latestFromBranch + branchName: refs/heads/main + artifact: drop_build_main + - task: ExtractFiles@1 + displayName: Extract PowerShellEditorServices + inputs: + archiveFilePatterns: $(Pipeline.Workspace)/PowerShellEditorServices.zip + destinationFolder: $(Build.SourcesDirectory)/modules + - pwsh: | + $manifest = Test-ModuleManifest $(Build.SourcesDirectory)/modules/PowerShellEditorServices/PowerShellEditorServices.psd1 + Write-Host Using PowerShellEditorServices v$($manifest.Version) + displayName: PowerShellEditorServices version + - pwsh: ./tools/installPSResources.ps1 -PSRepository CFS + displayName: Install PSResources + - pwsh: Invoke-Build Build -Configuration $(BuildConfiguration) + displayName: Build + - task: onebranch.pipeline.signing@1 + displayName: Sign 1st-party example PowerShell files + inputs: + command: sign + signing_profile: external_distribution + search_root: $(Build.SourcesDirectory)/examples + files_to_sign: "**/*.ps1;**/*.psd1;**/*.psm1" + - pwsh: Invoke-Build Package + displayName: Create package + - pwsh: | + npx vsce generate-manifest --packagePath out/powershell-$(package.vsixVersion).vsix + cp out/powershell-$(package.vsixVersion).manifest out/powershell-$(package.vsixVersion).signature.p7s + displayName: Generate VSIX manifest + - task: onebranch.pipeline.signing@1 + displayName: Sign VSIX manifest + inputs: + command: sign + cp_code: "CP-401405-VSCodePublisherSign" + search_root: $(Build.SourcesDirectory)/out + files_to_sign: | + *.signature.p7s; + - job: test + displayName: Build and run tests + pool: + type: windows + isCustom: true + name: Azure Pipelines + vmImage: windows-latest + variables: + ob_outputDirectory: $(Build.SourcesDirectory)/out + skipComponentGovernanceDetection: true + steps: + - task: UseNode@1 + displayName: Use Node 20.x + inputs: + version: 20.x + - task: UseDotNet@2 + displayName: Use .NET 8.x SDK + inputs: + packageType: sdk + version: 8.x + - task: DownloadPipelineArtifact@2 + displayName: Download PowerShellEditorServices + inputs: + source: specific + project: PowerShellCore + definition: 2905 + specificBuildWithTriggering: true + allowPartiallySucceededBuilds: true + buildVersionToDownload: latestFromBranch + branchName: refs/heads/main + artifact: drop_build_main + - task: ExtractFiles@1 + displayName: Extract PowerShellEditorServices + inputs: + archiveFilePatterns: $(Pipeline.Workspace)/PowerShellEditorServices.zip + destinationFolder: $(Build.SourcesDirectory)/modules + - pwsh: | + $manifest = Test-ModuleManifest $(Build.SourcesDirectory)/modules/PowerShellEditorServices/PowerShellEditorServices.psd1 + Write-Host Using PowerShellEditorServices v$($manifest.Version) + displayName: PowerShellEditorServices version + - pwsh: ./tools/installPSResources.ps1 -PSRepository CFS + displayName: Install PSResources + - pwsh: Invoke-Build Test -Configuration $(BuildConfiguration) + displayName: Run tests + - stage: release + dependsOn: build + condition: eq(variables['Build.Reason'], 'Manual') variables: - ob_outputDirectory: $(Build.SourcesDirectory)/out - ob_sdl_codeSignValidation_excludes: -|**\*.js # Node.js JavaScript signatures are not supported - steps: - - pwsh: | - $version = (Get-Content -Raw -Path package.json | ConvertFrom-Json).version - Write-Output "##vso[task.setvariable variable=vsixVersion;isOutput=true]$version" - $prerelease = ([semver]$version).Minor % 2 -ne 0 - if ($prerelease) { $version += "-preview" } - Write-Output "##vso[task.setvariable variable=version;isOutput=true]$version" - Write-Output "##vso[task.setvariable variable=prerelease;isOutput=true]$prerelease" - name: package - displayName: Get version from package.json - - task: onebranch.pipeline.version@1 - displayName: Set OneBranch version - inputs: - system: Custom - customVersion: $(package.version) - - task: UseNode@1 - displayName: Use Node 20.x - inputs: - version: 20.x - - task: DownloadPipelineArtifact@2 - displayName: Download PowerShellEditorServices - inputs: - source: specific - project: PowerShellCore - definition: 2905 - specificBuildWithTriggering: true - allowPartiallySucceededBuilds: true - buildVersionToDownload: latestFromBranch - branchName: refs/heads/main - artifact: drop_build_main - - task: ExtractFiles@1 - displayName: Extract PowerShellEditorServices - inputs: - archiveFilePatterns: $(Pipeline.Workspace)/PowerShellEditorServices.zip - destinationFolder: $(Build.SourcesDirectory)/modules - - pwsh: | - $manifest = Test-ModuleManifest $(Build.SourcesDirectory)/modules/PowerShellEditorServices/PowerShellEditorServices.psd1 - Write-Host Using PowerShellEditorServices v$($manifest.Version) - displayName: PowerShellEditorServices version - - pwsh: ./tools/installPSResources.ps1 -PSRepository CFS - displayName: Install PSResources - - pwsh: Invoke-Build Build -Configuration $(BuildConfiguration) - displayName: Build - - task: onebranch.pipeline.signing@1 - displayName: Sign 1st-party example PowerShell files - inputs: - command: sign - signing_profile: external_distribution - search_root: $(Build.SourcesDirectory)/examples - files_to_sign: '**/*.ps1;**/*.psd1;**/*.psm1' - - pwsh: Invoke-Build Package - displayName: Create package - - pwsh: | - npx vsce generate-manifest --packagePath out/powershell-$(package.vsixVersion).vsix - cp out/powershell-$(package.vsixVersion).manifest out/powershell-$(package.vsixVersion).signature.p7s - displayName: Generate VSIX manifest - - task: onebranch.pipeline.signing@1 - displayName: Sign VSIX manifest - inputs: - command: sign - cp_code: 'CP-401405-VSCodePublisherSign' - search_root: $(Build.SourcesDirectory)/out - files_to_sign: | - *.signature.p7s; - - job: test - displayName: Build and run tests - pool: - type: windows - isCustom: true - name: Azure Pipelines - vmImage: windows-latest - variables: - ob_outputDirectory: $(Build.SourcesDirectory)/out - skipComponentGovernanceDetection: true - steps: - - task: UseNode@1 - displayName: Use Node 20.x - inputs: - version: 20.x - - task: UseDotNet@2 - displayName: Use .NET 8.x SDK - inputs: - packageType: sdk - version: 8.x - - task: DownloadPipelineArtifact@2 - displayName: Download PowerShellEditorServices - inputs: - source: specific - project: PowerShellCore - definition: 2905 - specificBuildWithTriggering: true - allowPartiallySucceededBuilds: true - buildVersionToDownload: latestFromBranch - branchName: refs/heads/main - artifact: drop_build_main - - task: ExtractFiles@1 - displayName: Extract PowerShellEditorServices - inputs: - archiveFilePatterns: $(Pipeline.Workspace)/PowerShellEditorServices.zip - destinationFolder: $(Build.SourcesDirectory)/modules - - pwsh: | - $manifest = Test-ModuleManifest $(Build.SourcesDirectory)/modules/PowerShellEditorServices/PowerShellEditorServices.psd1 - Write-Host Using PowerShellEditorServices v$($manifest.Version) - displayName: PowerShellEditorServices version - - pwsh: ./tools/installPSResources.ps1 -PSRepository CFS - displayName: Install PSResources - - pwsh: Invoke-Build Test -Configuration $(BuildConfiguration) - displayName: Run tests - - stage: release - dependsOn: build - condition: eq(variables['Build.Reason'], 'Manual') - variables: - version: $[ stageDependencies.build.main.outputs['package.version'] ] - vsixVersion: $[ stageDependencies.build.main.outputs['package.vsixVersion'] ] - prerelease: $[ stageDependencies.build.main.outputs['package.prerelease'] ] - drop: $(Pipeline.Workspace)/drop_build_main - jobs: - - job: github - displayName: Publish draft to GitHub - pool: - type: windows - variables: - ob_outputDirectory: $(Build.SourcesDirectory)/out - steps: - - download: current - displayName: Download artifacts - - task: GitHubRelease@1 - displayName: Create GitHub release - inputs: - gitHubConnection: GitHub - repositoryName: PowerShell/vscode-powershell - target: main - assets: $(drop)/powershell-$(vsixVersion).vsix - tagSource: userSpecifiedTag - tag: v$(version) - isDraft: true - isPreRelease: $(prerelease) - addChangeLog: false - releaseNotesSource: inline - releaseNotesInline: "" - - job: validation - displayName: Manual validation - pool: - type: agentless - timeoutInMinutes: 1440 - steps: - - task: ManualValidation@0 - displayName: Wait 24 hours for validation - inputs: - notifyUsers: $(Build.RequestedForEmail) - instructions: Please validate the release and then publish it! + version: $[ stageDependencies.build.main.outputs['package.version'] ] + vsixVersion: $[ stageDependencies.build.main.outputs['package.vsixVersion'] ] + prerelease: $[ stageDependencies.build.main.outputs['package.prerelease'] ] + drop: $(Pipeline.Workspace)/drop_build_main + jobs: + - job: github + displayName: Publish draft to GitHub + pool: + type: windows + variables: + ob_outputDirectory: $(Build.SourcesDirectory)/out + steps: + - download: current + displayName: Download artifacts + - task: GitHubRelease@1 + displayName: Create GitHub release + inputs: + gitHubConnection: GitHub + repositoryName: PowerShell/vscode-powershell + target: main + assets: $(drop)/powershell-$(vsixVersion).vsix + tagSource: userSpecifiedTag + tag: v$(version) + isDraft: true + isPreRelease: $(prerelease) + addChangeLog: false + releaseNotesSource: inline + releaseNotesInline: "" + - job: validation + displayName: Manual validation + pool: + type: agentless timeoutInMinutes: 1440 - - job: vscode - dependsOn: validation - displayName: Publish to VS Code Marketplace - pool: - type: windows - variables: - ob_outputDirectory: $(Build.SourcesDirectory)/out - steps: - - download: current - displayName: Download artifacts - - task: UseNode@1 - displayName: Use Node 20.x - inputs: - version: 20.x - - pwsh: npm ci - displayName: Install NPM packages (for vsce) - - task: AzureCLI@2 - displayName: Run vsce publish - inputs: - azureSubscription: vscode-marketplace - scriptType: pscore - scriptLocation: inlineScript - inlineScript: | - $publishArgs = @( - '--azure-credential' - '--packagePath' - '$(drop)/powershell-$(vsixVersion).vsix' - '--manifestPath' - '$(drop)/powershell-$(vsixVersion).manifest' - '--signaturePath' - '$(drop)/powershell-$(vsixVersion).signature.p7s' - if ([bool]::Parse('$(prerelease)')) { '--pre-release' } - ) - npm run publish -- @publishArgs + steps: + - task: ManualValidation@0 + displayName: Wait 24 hours for validation + inputs: + notifyUsers: $(Build.RequestedForEmail) + instructions: Please validate the release and then publish it! + timeoutInMinutes: 1440 + - job: vscode + dependsOn: validation + displayName: Publish to VS Code Marketplace + pool: + type: windows + variables: + ob_outputDirectory: $(Build.SourcesDirectory)/out + steps: + - download: current + displayName: Download artifacts + - task: UseNode@1 + displayName: Use Node 20.x + inputs: + version: 20.x + - pwsh: npm ci + displayName: Install NPM packages (for vsce) + - task: AzureCLI@2 + displayName: Run vsce publish + inputs: + azureSubscription: vscode-marketplace + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + $publishArgs = @( + '--azure-credential' + '--packagePath' + '$(drop)/powershell-$(vsixVersion).vsix' + '--manifestPath' + '$(drop)/powershell-$(vsixVersion).manifest' + '--signaturePath' + '$(drop)/powershell-$(vsixVersion).signature.p7s' + if ([bool]::Parse('$(prerelease)')) { '--pre-release' } + ) + npm run publish -- @publishArgs diff --git a/eslint.config.mjs b/eslint.config.mjs index bf3be9452a..9ad447020d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -29,6 +29,7 @@ export default tseslint.config( "error", { argsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", }, ], "@typescript-eslint/no-unsafe-argument": "off", diff --git a/examples/.vscode/launch.json b/examples/.vscode/launch.json index 88687248dd..310016412d 100644 --- a/examples/.vscode/launch.json +++ b/examples/.vscode/launch.json @@ -1,59 +1,59 @@ { - "version": "0.2.0", - "configurations": [ - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File", - "script": "${file}", - "args": [], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File in Temporary Console", - "script": "${file}", - "args": [], - "cwd": "${file}", - "createTemporaryIntegratedConsole": true - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File w/Args Prompt", - "script": "${file}", - "args": [ "${command:SpecifyScriptArgs}" ], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch DebugTest.ps1", - "script": "${workspaceRoot}/DebugTest.ps1", - "args": ["-Count 55 -DelayMilliseconds 250"], - "cwd": "${workspaceRoot}" - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Interactive Session", - "cwd": "${workspaceRoot}" - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Pester Tests", - "script": "Invoke-Pester", - "args": [], - "cwd": "${workspaceRoot}" - }, - { - "type": "PowerShell", - "request": "attach", - "name": "PowerShell Attach to Host Process", - "processId": "${command:PickPSHostProcess}", - "runspaceId": 1 - } - ] + "version": "0.2.0", + "configurations": [ + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File", + "script": "${file}", + "args": [], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File in Temporary Console", + "script": "${file}", + "args": [], + "cwd": "${file}", + "createTemporaryIntegratedConsole": true + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File w/Args Prompt", + "script": "${file}", + "args": ["${command:SpecifyScriptArgs}"], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch DebugTest.ps1", + "script": "${workspaceRoot}/DebugTest.ps1", + "args": ["-Count 55 -DelayMilliseconds 250"], + "cwd": "${workspaceRoot}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Interactive Session", + "cwd": "${workspaceRoot}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Pester Tests", + "script": "Invoke-Pester", + "args": [], + "cwd": "${workspaceRoot}" + }, + { + "type": "PowerShell", + "request": "attach", + "name": "PowerShell Attach to Host Process", + "processId": "${command:PickPSHostProcess}", + "runspaceId": 1 + } + ] } diff --git a/examples/.vscode/settings.json b/examples/.vscode/settings.json index a37044b519..23c9990db9 100644 --- a/examples/.vscode/settings.json +++ b/examples/.vscode/settings.json @@ -1,6 +1,6 @@ { - // Use a custom PowerShell Script Analyzer settings file for this workspace. - // Relative paths for this setting are always relative to the workspace root dir. - "powershell.scriptAnalysis.settingsPath": "./PSScriptAnalyzerSettings.psd1", - "files.defaultLanguage": "powershell", + // Use a custom PowerShell Script Analyzer settings file for this workspace. + // Relative paths for this setting are always relative to the workspace root dir. + "powershell.scriptAnalysis.settingsPath": "./PSScriptAnalyzerSettings.psd1", + "files.defaultLanguage": "powershell" } diff --git a/examples/.vscode/tasks.json b/examples/.vscode/tasks.json index 9c5bf45ce1..8ae38829cc 100644 --- a/examples/.vscode/tasks.json +++ b/examples/.vscode/tasks.json @@ -30,73 +30,60 @@ // ${cwd} the task runner's current working directory on startup // ${lineNumber} the current selected line number in the active file { - "version": "2.0.0", - "windows": { - "options": { - "shell": { - "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", - "args": [ - "-NoProfile", - "-ExecutionPolicy", - "Bypass", - "-Command" - ] - } - } + "version": "2.0.0", + "windows": { + "options": { + "shell": { + "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"] + } + } + }, + "linux": { + "options": { + "shell": { + "executable": "/usr/bin/pwsh", + "args": ["-NoProfile", "-Command"] + } + } + }, + "osx": { + "options": { + "shell": { + "executable": "/usr/local/bin/pwsh", + "args": ["-NoProfile", "-Command"] + } + } + }, + "tasks": [ + { + "label": "Clean", + "type": "shell", + "command": "Invoke-PSake build.ps1 -taskList Clean" }, - "linux": { - "options": { - "shell": { - "executable": "/usr/bin/pwsh", - "args": [ - "-NoProfile", - "-Command" - ] - } - } + { + "label": "Build", + "type": "shell", + "command": "Invoke-PSake build.ps1 -taskList Build", + "group": { + "kind": "build", + "isDefault": true + } }, - "osx": { - "options": { - "shell": { - "executable": "/usr/local/bin/pwsh", - "args": [ - "-NoProfile", - "-Command" - ] - } - } + { + "label": "Test", + "type": "shell", + "command": "Invoke-Pester -PesterOption @{IncludeVSCodeMarker=$true}", + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": ["$pester"] }, - "tasks": [ - { - "label": "Clean", - "type": "shell", - "command": "Invoke-PSake build.ps1 -taskList Clean" - }, - { - "label": "Build", - "type": "shell", - "command": "Invoke-PSake build.ps1 -taskList Build", - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "Test", - "type": "shell", - "command": "Invoke-Pester -PesterOption @{IncludeVSCodeMarker=$true}", - "group": { - "kind": "test", - "isDefault": true - }, - "problemMatcher": [ - "$pester" - ] - }, - { - "label": "Publish", - "type": "shell", - "command": "Invoke-PSake build.ps1 -taskList Publish" - } - ] + { + "label": "Publish", + "type": "shell", + "command": "Invoke-PSake build.ps1 -taskList Publish" + } + ] } diff --git a/package.json b/package.json index 456153fee8..593678485a 100644 --- a/package.json +++ b/package.json @@ -104,13 +104,17 @@ "compile": "esbuild ./src/extension.ts --outdir=dist --sourcemap --bundle --external:vscode --platform=node", "watch": "npm run compile -- --watch", "lint": "eslint src test --ext .ts", + "format": "prettier --check '**/*.{ts,json,yml,mjs,code-workspace}'", "package": "vsce package --out out/ --no-gitHubIssueLinking", "publish": "vsce publish", "pretest": "npm run compile", "test": "vscode-test" }, "prettier": { - "plugins": ["prettier-plugin-organize-imports"] + "endOfLine": "auto", + "plugins": [ + "prettier-plugin-organize-imports" + ] }, "contributes": { "breakpoints": [ diff --git a/pwsh-extension-dev.code-workspace b/pwsh-extension-dev.code-workspace index 5dddd29e64..dc4208242f 100644 --- a/pwsh-extension-dev.code-workspace +++ b/pwsh-extension-dev.code-workspace @@ -2,12 +2,12 @@ "folders": [ { "name": "Client", - "path": "." + "path": ".", }, { "name": "Server", - "path": "../PowerShellEditorServices" - } + "path": "../PowerShellEditorServices", + }, ], "extensions": { "recommendations": [ @@ -18,27 +18,28 @@ "ms-dotnettools.csharp", "ms-vscode.powershell", "connor4312.esbuild-problem-matchers", - "ms-vscode.extension-test-runner" - ] + "ms-vscode.extension-test-runner", + ], }, "settings": { "window.title": "PowerShell VS Code Extension Development", "debug.onTaskErrors": "prompt", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modifications", + "editor.formatOnPaste": true, "editor.codeActionsOnSave": { "source.fixAll": "explicit", }, - "[typescript][javascript][json]": { + "[typescript][javascript][json][jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "editor.formatOnSaveMode": "modificationsIfAvailable", + "editor.formatOnSaveMode": "file", }, "files.associations": { "**/snippets/*.json": "jsonc", // Use JSONC instead of JSON because that's how VS Code interprets snippet files, and it enables better source documentation. }, // Ignore the Markdown rule: "markdownlint.config": { - "MD024": false // no-duplicate-header + "MD024": false, // no-duplicate-header }, "powershell.cwd": "Client", "powershell.codeFormatting.autoCorrectAliases": true, @@ -59,36 +60,25 @@ "options": { "shell": { "executable": "pwsh.exe", - "args": [ - "-NoProfile", - "-ExecutionPolicy", - "Bypass", - "-Command" - ] - } - } + "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"], + }, + }, }, "linux": { "options": { "shell": { "executable": "pwsh", - "args": [ - "-NoProfile", - "-Command" - ] - } - } + "args": ["-NoProfile", "-Command"], + }, + }, }, "osx": { "options": { "shell": { "executable": "pwsh", - "args": [ - "-NoProfile", - "-Command" - ] - } - } + "args": ["-NoProfile", "-Command"], + }, + }, }, "tasks": [ { @@ -98,24 +88,21 @@ }, "type": "shell", "options": { - "cwd": "${workspaceFolder:Client}" + "cwd": "${workspaceFolder:Client}", }, "command": "Invoke-Build Build", - "problemMatcher": [ - "$msCompile", - "$tsc" - ], + "problemMatcher": ["$msCompile", "$tsc"], "group": { "kind": "build", - "isDefault": true - } + "isDefault": true, + }, }, { "label": "Extension: Watch", "type": "shell", "isBackground": true, "options": { - "cwd": "${workspaceFolder:Client}" + "cwd": "${workspaceFolder:Client}", }, "command": "npm run watch", "problemMatcher": { @@ -130,104 +117,90 @@ "background": { "activeOnStart": true, "beginsPattern": "^\\[watch\\] build started", - "endsPattern": "^\\[watch\\] build finished" - } + "endsPattern": "^\\[watch\\] build finished", + }, }, "icon": { "id": "sync", - "color": "terminal.ansiCyan" + "color": "terminal.ansiCyan", }, "group": { "kind": "build", - } + }, }, { "label": "PSES: BuildIfChanged", "type": "shell", "options": { - "cwd": "${workspaceFolder:Server}" + "cwd": "${workspaceFolder:Server}", }, "command": "Invoke-Build BuildIfChanged", "problemMatcher": "$msCompile", "icon": { "id": "tools", - "color": "terminal.ansiCyan" + "color": "terminal.ansiCyan", }, "group": { "kind": "build", - } + }, }, { "label": "PreLaunch", - "dependsOn": [ - "Extension: Watch", - "PSES: BuildIfChanged" - ], - "dependsOrder": "parallel" + "dependsOn": ["Extension: Watch", "PSES: BuildIfChanged"], + "dependsOrder": "parallel", }, { "label": "Test Client", "type": "shell", "options": { - "cwd": "${workspaceFolder:Client}" + "cwd": "${workspaceFolder:Client}", }, "command": "Invoke-Build Test", - "problemMatcher": [ - "$msCompile", - "$tsc" - ], + "problemMatcher": ["$msCompile", "$tsc"], "group": { "kind": "test", - "isDefault": true - } + "isDefault": true, + }, }, { "label": "Test Server", "type": "shell", "options": { - "cwd": "${workspaceFolder:Server}" + "cwd": "${workspaceFolder:Server}", }, - "problemMatcher": [ - "$msCompile" - ], + "problemMatcher": ["$msCompile"], "command": "Invoke-Build TestPS74", "group": { "kind": "test", - "isDefault": true - } + "isDefault": true, + }, }, { "label": "Invoke-Build Client", "type": "shell", "options": { - "cwd": "${workspaceFolder:Client}" + "cwd": "${workspaceFolder:Client}", }, "command": "Invoke-Build ${input:clientBuildCommand}", - "group": "build" + "group": "build", }, { "label": "Invoke-Build Server", "type": "shell", "options": { - "cwd": "${workspaceFolder:Server}" + "cwd": "${workspaceFolder:Server}", }, "command": "Invoke-Build ${input:serverBuildCommand}", - "group": "build" - } + "group": "build", + }, ], "inputs": [ { "type": "pickString", "id": "clientBuildCommand", "description": "Which Invoke-Build Client Task?", - "options": [ - "Restore", - "Clean", - "Build", - "Test", - "Package" - ], - "default": "Clean" + "options": ["Restore", "Clean", "Build", "Test", "Package"], + "default": "Clean", }, { "type": "pickString", @@ -244,56 +217,46 @@ "TestPS51", "TestE2EPowerShell", ], - "default": "Clean" - } + "default": "Clean", + }, ], - }, "launch": { "version": "0.2.0", "compounds": [ { "name": "Launch Extension", - "configurations": [ - "Launch", - "PowerShell Editor Services" - ], + "configurations": ["Launch", "PowerShell Editor Services"], "preLaunchTask": "Build", "stopAll": true, "presentation": { "hidden": false, "group": "Test", - "order": 1 - } + "order": 1, + }, }, { "name": "Launch Extension - Temp Profile", - "configurations": [ - "Launch-Temp", - "PowerShell Editor Services" - ], + "configurations": ["Launch-Temp", "PowerShell Editor Services"], "preLaunchTask": "PreLaunch", "stopAll": true, "presentation": { "hidden": false, "group": "Test", - "order": 2 - } + "order": 2, + }, }, { "name": "Launch Extension - Isolated Profile", - "configurations": [ - "Launch-Isolated", - "PowerShell Editor Services" - ], + "configurations": ["Launch-Isolated", "PowerShell Editor Services"], "preLaunchTask": "Build", "stopAll": true, "presentation": { "hidden": false, "group": "Test", - "order": 3 - } - } + "order": 3, + }, + }, ], "configurations": [ { @@ -306,22 +269,20 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder:Client}", - "${workspaceFolder:Client}/examples" + "${workspaceFolder:Client}/examples", ], "sourceMaps": true, // This speeds up source map detection and makes smartStep work correctly - "outFiles": [ - "${workspaceFolder:Client}/dist/*.js" - ], + "outFiles": ["${workspaceFolder:Client}/dist/*.js"], "skipFiles": [ "/**", "**/node_modules/**", "**/.vscode-test/**", - "**/app/out/vs/**" // Skips Extension Host internals + "**/app/out/vs/**", // Skips Extension Host internals ], "presentation": { - "hidden": true - } + "hidden": true, + }, }, { "name": "Launch-Temp", @@ -336,13 +297,11 @@ // Undocumented: https://github.com/microsoft/vscode-docs/issues/6220 "--profile-temp", "--extensionDevelopmentPath=${workspaceFolder:Client}", - "${workspaceFolder:Client}/examples" + "${workspaceFolder:Client}/examples", ], "sourceMaps": true, // This speeds up source map detection and makes smartStep work correctly - "outFiles": [ - "${workspaceFolder:Client}/dist/*.js" - ], + "outFiles": ["${workspaceFolder:Client}/dist/*.js"], "skipFiles": [ "/**", "**/node_modules/**", @@ -350,8 +309,8 @@ "**/app/out/vs/**", // Skips Extension Host internals ], "presentation": { - "hidden": true - } + "hidden": true, + }, }, { "name": "Launch-Isolated", @@ -366,22 +325,20 @@ // Undocumented: https://github.com/microsoft/vscode-docs/issues/6220 "--profile=pwsh-debug", "--extensionDevelopmentPath=${workspaceFolder:Client}", - "${workspaceFolder:Client}/examples" + "${workspaceFolder:Client}/examples", ], "sourceMaps": true, // This speeds up source map detection and makes smartStep work correctly - "outFiles": [ - "${workspaceFolder:Client}/dist/*.js" - ], + "outFiles": ["${workspaceFolder:Client}/dist/*.js"], "skipFiles": [ "/**", "**/node_modules/**", "**/.vscode-test/**", - "**/app/out/vs/**" // Skips Extension Host internals + "**/app/out/vs/**", // Skips Extension Host internals ], "presentation": { - "hidden": true - } + "hidden": true, + }, }, { // https://code.visualstudio.com/docs/csharp/debugger-settings @@ -394,15 +351,15 @@ "symbolOptions": { "searchPaths": [], "searchMicrosoftSymbolServer": false, - "searchNuGetOrgSymbolServer": false + "searchNuGetOrgSymbolServer": false, }, "presentation": { "hidden": false, "group": "Test", - "order": 5 + "order": 5, }, "logging": { - "moduleLoad": false + "moduleLoad": false, }, }, { @@ -417,14 +374,14 @@ "symbolOptions": { "searchPaths": [], "searchMicrosoftSymbolServer": false, - "searchNuGetOrgSymbolServer": false + "searchNuGetOrgSymbolServer": false, }, "presentation": { - "hidden": true + "hidden": true, }, "logging": { - "moduleLoad": false - } + "moduleLoad": false, + }, }, { // Runs the extension in an isolated but persistent profile separate from the user settings @@ -436,22 +393,20 @@ "args": [ "--profile=debug", "--extensionDevelopmentPath=${workspaceFolder:Client}", - "${workspaceFolder:Server}/test/PowerShellEditorServices.Test.Shared/Refactoring" + "${workspaceFolder:Server}/test/PowerShellEditorServices.Test.Shared/Refactoring", ], "sourceMaps": true, // This speeds up source map detection and makes smartStep work correctly - "outFiles": [ - "${workspaceFolder:Client}/dist/*.js" - ], + "outFiles": ["${workspaceFolder:Client}/dist/*.js"], "skipFiles": [ "/**", "**/node_modules/**", - "**/.vscode-test/**" // Skips Extension Host internals + "**/.vscode-test/**", // Skips Extension Host internals ], "presentation": { "hidden": true, - } + }, }, - ] - } + ], + }, } diff --git a/snippets/PowerShell.json b/snippets/PowerShell.json index d0a94ba30b..61fa010c85 100644 --- a/snippets/PowerShell.json +++ b/snippets/PowerShell.json @@ -38,10 +38,7 @@ ] }, "Class Constructor": { - "prefix": [ - "ctor-class", - "class-constructor" - ], + "prefix": ["ctor-class", "class-constructor"], "description": "Set default values and validate object logic at the moment of creating the instance of the class. Constructors have the same name as the class. Constructors might have arguments, to initialize the data members of the new object. More: Get-Help about_Classes", "body": [ "${1:ClassName}(${2:<#OptionalParameters#>}) {", @@ -61,14 +58,10 @@ "Class Property": { "prefix": "class-property", "description": "Properties are variables declared at class scope. A property may be of any built-in type or an instance of another class. Classes have no restriction in the number of properties they have. More: Get-Help about_Classes", - "body": [ - "[${1:propertyType}] $${0:PropertyName}" - ] + "body": ["[${1:propertyType}] $${0:PropertyName}"] }, "Comment Block": { - "prefix": [ - "block-comment" - ], + "prefix": ["block-comment"], "description": "A multi-line comment.", "body": [ "$BLOCK_COMMENT_START", @@ -202,10 +195,7 @@ ] }, "Function Help": { - "prefix": [ - "help-function", - "comment-help" - ], + "prefix": ["help-function", "comment-help"], "description": "Comment-based help for an advanced function. More: Get-Help about_Comment_Based_Help", "body": [ "<#", @@ -226,10 +216,7 @@ ] }, "Function-Advanced": { - "prefix": [ - "function-advanced", - "cmdlet" - ], + "prefix": ["function-advanced", "cmdlet"], "description": "Script advanced function definition snippet. More: Get-Help about_Functions_Advanced", "body": [ "function ${1:Verb-Noun} {", @@ -262,10 +249,7 @@ ] }, "Function: Suppress PSScriptAnalyzer Rule": { - "prefix": [ - "suppress-message-rule-function", - "[SuppressMessageAttribute]" - ], + "prefix": ["suppress-message-rule-function", "[SuppressMessageAttribute]"], "description": "Suppress a PSScriptAnalyzer rule for a function. More: https://docs.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules#suppressing-rules", "body": [ "[Diagnostics.CodeAnalysis.SuppressMessageAttribute(", @@ -278,44 +262,22 @@ "Hashtable": { "prefix": "hashtable", "description": "A key/value store that are very efficient for finding and retrieving data. More: Get-Help about_Hash_Tables", - "body": [ - "\\$${1:Var} = @{", - "\t${2:Name} = ${3:Value}", - "}" - ] + "body": ["\\$${1:Var} = @{", "\t${2:Name} = ${3:Value}", "}"] }, "Here-String": { - "prefix": [ - "hs", - "here-string" - ], + "prefix": ["hs", "here-string"], "description": "Escape all text but evaluate variables. More: Get-Help about_Quoting_Rules", - "body": [ - "@\"", - "${0:TM_SELECTED_TEXT}", - "\"@", - "" - ] + "body": ["@\"", "${0:TM_SELECTED_TEXT}", "\"@", ""] }, "Here-String (Literal)": { - "prefix": [ - "hsl", - "literal-here-string" - ], + "prefix": ["hsl", "literal-here-string"], "description": "Escape all text literally. More: Get-Help about_Quoting_Rules", - "body": [ - "@'", - "${0:TM_SELECTED_TEXT}", - "'@", - "" - ] + "body": ["@'", "${0:TM_SELECTED_TEXT}", "'@", ""] }, "Hidden Property": { "prefix": "class-proph-hidden", "description": "Useful for creating internal properties and methods within a class that are hidden from users. More: Get-Help about_Hidden", - "body": [ - "hidden [${1:string}] $${0:PropertyName}" - ] + "body": ["hidden [${1:string}] $${0:PropertyName}"] }, "IArgumentCompleter Class": { "prefix": "iargument-completer", @@ -518,10 +480,7 @@ ] }, "Parameter: Suppress PSScriptAnalyzer Rule": { - "prefix": [ - "suppress-message-rule-parameter", - "[SuppressMessageAttribute]" - ], + "prefix": ["suppress-message-rule-parameter", "[SuppressMessageAttribute]"], "description": "Suppress a PSScriptAnalyzer rule on a parameter. More: https://docs.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules#suppressing-rules", "body": [ "[Diagnostics.CodeAnalysis.SuppressMessageAttribute(", @@ -548,31 +507,17 @@ ] }, "PSCustomObject": { - "prefix": [ - "pscustomobject", - "[PSCustomObject]" - ], + "prefix": ["pscustomobject", "[PSCustomObject]"], "description": "Create a custom object from a hashtable of properties. More: Get-Help about_PSCustomObject", - "body": [ - "[PSCustomObject]@{", - "\t${1:Name} = ${2:Value}", - "}" - ] + "body": ["[PSCustomObject]@{", "\t${1:Name} = ${2:Value}", "}"] }, "Region Block": { "prefix": "region", "description": "Region block for organizing and folding of your code", - "body": [ - "#region ${1}", - "${0:$TM_SELECTED_TEXT}", - "#endregion" - ] + "body": ["#region ${1}", "${0:$TM_SELECTED_TEXT}", "#endregion"] }, "Scope: Suppress PSScriptAnalyzer Rule": { - "prefix": [ - "suppress-message-rule-scope", - "[SuppressMessageAttribute]" - ], + "prefix": ["suppress-message-rule-scope", "[SuppressMessageAttribute]"], "description": "Suppress a PSScriptAnalyzer rule based on a function/parameter/class/variable/object's name by setting the SuppressMessageAttribute's Target property to a regular expression or a glob pattern. More: https://docs.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules#suppressing-rules", "body": [ "[Diagnostics.CodeAnalysis.SuppressMessageAttribute(", @@ -594,10 +539,7 @@ ] }, "Suppress PSScriptAnalyzer Rule": { - "prefix": [ - "suppress-message-rule", - "[SuppressMessageAttribute]" - ], + "prefix": ["suppress-message-rule", "[SuppressMessageAttribute]"], "description": "Suppress a PSScriptAnalyzer rule. More: https://docs.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules#suppressing-rules", "body": [ "[Diagnostics.CodeAnalysis.SuppressMessageAttribute(", @@ -665,10 +607,6 @@ "while": { "prefix": "while", "description": "Repeatedly perform an action after verifying a condition is true first. More: Get-Help about_While", - "body": [ - "while (${1:condition}) {", - "\t${0:$TM_SELECTED_TEXT}", - "}" - ] + "body": ["while (${1:condition}) {", "\t${0:$TM_SELECTED_TEXT}", "}"] } -} \ No newline at end of file +} diff --git a/src/controls/checkboxQuickPick.ts b/src/controls/checkboxQuickPick.ts index e318992365..2c8bf72c07 100644 --- a/src/controls/checkboxQuickPick.ts +++ b/src/controls/checkboxQuickPick.ts @@ -6,7 +6,8 @@ import vscode = require("vscode"); const confirmItemLabel = "$(checklist) Confirm"; const checkedPrefix = "[ $(check) ]"; const uncheckedPrefix = "[ ]"; -const defaultPlaceHolder = "Select 'Confirm' to confirm or press 'Esc' key to cancel"; +const defaultPlaceHolder = + "Select 'Confirm' to confirm or press 'Esc' key to cancel"; export interface ICheckboxQuickPickItem { label: string; @@ -18,17 +19,21 @@ export interface ICheckboxQuickPickOptions { confirmPlaceHolder: string; } -const defaultOptions: ICheckboxQuickPickOptions = { confirmPlaceHolder: defaultPlaceHolder }; +const defaultOptions: ICheckboxQuickPickOptions = { + confirmPlaceHolder: defaultPlaceHolder, +}; export async function showCheckboxQuickPick( items: ICheckboxQuickPickItem[], - options: ICheckboxQuickPickOptions = defaultOptions): Promise { - + options: ICheckboxQuickPickOptions = defaultOptions, +): Promise { const selectedItem = await showInner(items, options); return selectedItem !== undefined ? items : undefined; } -function getQuickPickItems(items: ICheckboxQuickPickItem[]): vscode.QuickPickItem[] { +function getQuickPickItems( + items: ICheckboxQuickPickItem[], +): vscode.QuickPickItem[] { const quickPickItems: vscode.QuickPickItem[] = []; quickPickItems.push({ label: confirmItemLabel, description: "" }); @@ -44,15 +49,16 @@ function getQuickPickItems(items: ICheckboxQuickPickItem[]): vscode.QuickPickIte async function showInner( items: ICheckboxQuickPickItem[], - options: ICheckboxQuickPickOptions): Promise { - + options: ICheckboxQuickPickOptions, +): Promise { const selection = await vscode.window.showQuickPick( getQuickPickItems(items), { ignoreFocusOut: true, matchOnDescription: true, placeHolder: options.confirmPlaceHolder, - }); + }, + ); if (selection === undefined) { return undefined; @@ -66,13 +72,18 @@ async function showInner( if (index >= 0) { toggleSelection(items[index]); } else { - console.log(`Couldn't find CheckboxQuickPickItem for label '${selection.label}'`); + console.log( + `Couldn't find CheckboxQuickPickItem for label '${selection.label}'`, + ); } return showInner(items, options); } -function getItemIndex(items: ICheckboxQuickPickItem[], itemLabel: string): number { +function getItemIndex( + items: ICheckboxQuickPickItem[], + itemLabel: string, +): number { const trimmedLabel = itemLabel.substring(itemLabel.indexOf("]") + 2); return items.findIndex((item) => item.label === trimmedLabel); } diff --git a/src/extension.ts b/src/extension.ts index c311d06e7c..53fe03af19 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,11 +6,17 @@ import TelemetryReporter from "@vscode/extension-telemetry"; import { DocumentSelector } from "vscode-languageclient"; import { CodeActionsFeature } from "./features/CodeActions"; import { ConsoleFeature } from "./features/Console"; -import { DebugSessionFeature } from "./features/DebugSession"; +import { + DebugSessionFeature, + SpecifyScriptArgsFeature, +} from "./features/DebugSession"; import { ExamplesFeature } from "./features/Examples"; import { ExpandAliasFeature } from "./features/ExpandAlias"; import { ExtensionCommandsFeature } from "./features/ExtensionCommands"; -import { ExternalApiFeature, type IPowerShellExtensionClient } from "./features/ExternalApi"; +import { + ExternalApiFeature, + type IPowerShellExtensionClient, +} from "./features/ExternalApi"; import { GenerateBugReportFeature } from "./features/GenerateBugReport"; import { GetCommandsFeature } from "./features/GetCommands"; import { HelpCompletionFeature } from "./features/HelpCompletion"; @@ -19,15 +25,15 @@ import { OpenInISEFeature } from "./features/OpenInISE"; import { PesterTestsFeature } from "./features/PesterTests"; import { RemoteFilesFeature } from "./features/RemoteFiles"; import { ShowHelpFeature } from "./features/ShowHelp"; -import { SpecifyScriptArgsFeature } from "./features/DebugSession"; +import { LanguageClientConsumer } from "./languageClientConsumer"; import { Logger } from "./logging"; import { SessionManager } from "./session"; import { getSettings } from "./settings"; import { PowerShellLanguageId } from "./utils"; -import { LanguageClientConsumer } from "./languageClientConsumer"; // The 1DS telemetry key, which is just shared among all Microsoft extensions // (and isn't sensitive). -const TELEMETRY_KEY = "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255"; +const TELEMETRY_KEY = + "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255"; let languageConfigurationDisposable: vscode.Disposable; let logger: Logger; @@ -41,7 +47,9 @@ const documentSelector: DocumentSelector = [ { language: "powershell", scheme: "untitled" }, ]; -export async function activate(context: vscode.ExtensionContext): Promise { +export async function activate( + context: vscode.ExtensionContext, +): Promise { logger = new Logger(); if (context.extensionMode === vscode.ExtensionMode.Development) { restartOnExtensionFileChanges(context); @@ -50,14 +58,17 @@ export async function activate(context: vscode.ExtensionContext): Promise\/\?\s]+)/g, + wordPattern: + // eslint-disable-next-line no-useless-escape + /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\'\"\,\.\<\>\/\?\s]+)/g, indentationRules: { // ^(.*\*/)?\s*\}.*$ @@ -83,34 +94,50 @@ export async function activate(context: vscode.ExtensionContext): Promise {await vscode.commands.executeCommand( - "vscode.openFolder", - context.logUri, - { forceNewWindow: true } - );} - ), - vscode.commands.registerCommand( - "PowerShell.ShowLogs", - () => {logger.showLogPanel();} + async () => { + await vscode.commands.executeCommand( + "vscode.openFolder", + context.logUri, + { forceNewWindow: true }, + ); + }, ), + vscode.commands.registerCommand("PowerShell.ShowLogs", () => { + logger.showLogPanel(); + }), vscode.commands.registerCommand( "GetVsCodeSessionId", - () => vscode.env.sessionId + () => vscode.env.sessionId, ), // Register a command that waits for the Extension Terminal to be active. Can be used by .NET Attach Tasks. - registerWaitForPsesActivationCommand(context) + registerWaitForPsesActivationCommand(context), ]; const externalApi = new ExternalApiFeature(context, sessionManager, logger); @@ -182,22 +211,31 @@ export async function activate(context: vscode.ExtensionContext): Promise externalApi.registerExternalExtension(id, apiVersion), - unregisterExternalExtension: uuid => externalApi.unregisterExternalExtension(uuid), - getPowerShellVersionDetails: uuid => externalApi.getPowerShellVersionDetails(uuid), - waitUntilStarted: uuid => externalApi.waitUntilStarted(uuid), + registerExternalExtension: (id: string, apiVersion = "v1") => + externalApi.registerExternalExtension(id, apiVersion), + unregisterExternalExtension: (uuid) => + externalApi.unregisterExternalExtension(uuid), + getPowerShellVersionDetails: (uuid) => + externalApi.getPowerShellVersionDetails(uuid), + waitUntilStarted: (uuid) => externalApi.waitUntilStarted(uuid), getStorageUri: () => externalApi.getStorageUri(), getLogUri: () => externalApi.getLogUri(), }; } /** Registers a command that waits for PSES Activation and returns the PID, used to auto-attach the PSES debugger */ -function registerWaitForPsesActivationCommand(context: vscode.ExtensionContext): vscode.Disposable { +function registerWaitForPsesActivationCommand( + context: vscode.ExtensionContext, +): vscode.Disposable { return vscode.commands.registerCommand( "PowerShell.WaitForPsesActivationAndReturnProcessId", async () => { const pidFileName = `PSES-${vscode.env.sessionId}.pid`; - const pidFile = vscode.Uri.joinPath(context.globalStorageUri, "sessions", pidFileName); + const pidFile = vscode.Uri.joinPath( + context.globalStorageUri, + "sessions", + pidFileName, + ); const fs = vscode.workspace.fs; // Wait for the file to be created // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -219,22 +257,24 @@ function registerWaitForPsesActivationCommand(context: vscode.ExtensionContext): return pidContent.toString(); } catch { // File doesn't exist yet, wait and try again - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); } } - } + }, ); } /** Restarts the extension host when extension file changes are detected. Useful for development. */ function restartOnExtensionFileChanges(context: vscode.ExtensionContext): void { const watcher = vscode.workspace.createFileSystemWatcher( - new vscode.RelativePattern(context.extensionPath, "dist/*.js") + new vscode.RelativePattern(context.extensionPath, "dist/*.js"), ); context.subscriptions.push(watcher); watcher.onDidChange(({ fsPath }) => { - vscode.window.showInformationMessage(`${fsPath.split(context.extensionPath, 2)[1]} changed. Reloading Extension Host...`); + vscode.window.showInformationMessage( + `${fsPath.split(context.extensionPath, 2)[1]} changed. Reloading Extension Host...`, + ); vscode.commands.executeCommand("workbench.action.restartExtensionHost"); }); } diff --git a/src/features/CodeActions.ts b/src/features/CodeActions.ts index f9b022ed8c..825b7941a9 100644 --- a/src/features/CodeActions.ts +++ b/src/features/CodeActions.ts @@ -13,10 +13,12 @@ export class CodeActionsFeature implements vscode.Disposable { // // TODO: In the far future with LSP 3.19 the server can just set a URL // and this can go away. See https://github.com/microsoft/language-server-protocol/issues/1548 - this.command = - vscode.commands.registerCommand("PowerShell.ShowCodeActionDocumentation", async (ruleName: string) => { + this.command = vscode.commands.registerCommand( + "PowerShell.ShowCodeActionDocumentation", + async (ruleName: string) => { await this.showRuleDocumentation(ruleName); - }); + }, + ); } public dispose(): void { @@ -24,10 +26,13 @@ export class CodeActionsFeature implements vscode.Disposable { } private async showRuleDocumentation(ruleId: string): Promise { - const pssaDocBaseURL = "https://docs.microsoft.com/powershell/utility-modules/psscriptanalyzer/rules/"; + const pssaDocBaseURL = + "https://docs.microsoft.com/powershell/utility-modules/psscriptanalyzer/rules/"; if (!ruleId) { - this.log.writeWarning("Cannot show documentation for code action, no ruleName was supplied."); + this.log.writeWarning( + "Cannot show documentation for code action, no ruleName was supplied.", + ); return; } @@ -35,6 +40,9 @@ export class CodeActionsFeature implements vscode.Disposable { ruleId = ruleId.substring(2); } - await vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(pssaDocBaseURL + ruleId)); + await vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse(pssaDocBaseURL + ruleId), + ); } } diff --git a/src/features/Console.ts b/src/features/Console.ts index 4063e2bbe5..f7038f5ec1 100644 --- a/src/features/Console.ts +++ b/src/features/Console.ts @@ -5,23 +5,32 @@ import vscode = require("vscode"); import { NotificationType, RequestType } from "vscode-languageclient"; import { LanguageClient } from "vscode-languageclient/node"; import { - type ICheckboxQuickPickItem, - showCheckboxQuickPick, + type ICheckboxQuickPickItem, + showCheckboxQuickPick, } from "../controls/checkboxQuickPick"; +import { LanguageClientConsumer } from "../languageClientConsumer"; import type { ILogger } from "../logging"; import { getSettings } from "../settings"; -import { LanguageClientConsumer } from "../languageClientConsumer"; - -export const EvaluateRequestType = new RequestType("evaluate"); -export const OutputNotificationType = new NotificationType("output"); - -export const ShowChoicePromptRequestType = - new RequestType("powerShell/showChoicePrompt"); -export const ShowInputPromptRequestType = - new RequestType("powerShell/showInputPrompt"); +export const EvaluateRequestType = new RequestType< + IEvaluateRequestArguments, + void, + void +>("evaluate"); +export const OutputNotificationType = + new NotificationType("output"); + +export const ShowChoicePromptRequestType = new RequestType< + IShowChoicePromptRequestArgs, + IShowChoicePromptResponseBody, + string +>("powerShell/showChoicePrompt"); + +export const ShowInputPromptRequestType = new RequestType< + IShowInputPromptRequestArgs, + IShowInputPromptResponseBody, + string +>("powerShell/showInputPrompt"); export interface IEvaluateRequestArguments { expression: string; @@ -60,27 +69,29 @@ interface IShowInputPromptResponseBody { promptCancelled: boolean; } - -function showChoicePrompt(promptDetails: IShowChoicePromptRequestArgs): Thenable { - +function showChoicePrompt( + promptDetails: IShowChoicePromptRequestArgs, +): Thenable { let resultThenable: Thenable; if (!promptDetails.isMultiChoice) { - let quickPickItems = - promptDetails.choices.map((choice) => { + let quickPickItems = promptDetails.choices.map( + (choice) => { return { label: choice.label, description: choice.helpMessage, }; - }); + }, + ); if (promptDetails.defaultChoices.length > 0) { // Shift the default items to the front of the // array so that the user can select it easily const defaultChoice = promptDetails.defaultChoices[0]; - if (defaultChoice > -1 && - defaultChoice < promptDetails.choices.length) { - + if ( + defaultChoice > -1 && + defaultChoice < promptDetails.choices.length + ) { const defaultChoiceItem = quickPickItems[defaultChoice]; quickPickItems.splice(defaultChoice, 1); @@ -89,12 +100,11 @@ function showChoicePrompt(promptDetails: IShowChoicePromptRequestArgs): Thenable } } - resultThenable = - vscode.window - .showQuickPick( - quickPickItems, - { placeHolder: promptDetails.message }) - .then(onItemSelected); + resultThenable = vscode.window + .showQuickPick(quickPickItems, { + placeHolder: promptDetails.message, + }) + .then(onItemSelected); } else { const checkboxQuickPickItems = promptDetails.choices.map((choice) => { @@ -110,26 +120,33 @@ function showChoicePrompt(promptDetails: IShowChoicePromptRequestArgs): Thenable checkboxQuickPickItems[choice].isSelected = true; } - resultThenable = - showCheckboxQuickPick( - checkboxQuickPickItems, - { confirmPlaceHolder: promptDetails.message }) - .then(onItemsSelected); + resultThenable = showCheckboxQuickPick(checkboxQuickPickItems, { + confirmPlaceHolder: promptDetails.message, + }).then(onItemsSelected); } return resultThenable; } -async function showInputPrompt(promptDetails: IShowInputPromptRequestArgs): Promise { - const responseText = await vscode.window.showInputBox({ placeHolder: promptDetails.name + ": " }); +async function showInputPrompt( + promptDetails: IShowInputPromptRequestArgs, +): Promise { + const responseText = await vscode.window.showInputBox({ + placeHolder: promptDetails.name + ": ", + }); return onInputEntered(responseText); } -function onItemsSelected(chosenItems: ICheckboxQuickPickItem[] | undefined): IShowChoicePromptResponseBody { +function onItemsSelected( + chosenItems: ICheckboxQuickPickItem[] | undefined, +): IShowChoicePromptResponseBody { if (chosenItems !== undefined) { return { promptCancelled: false, - responseText: chosenItems.filter((item) => item.isSelected).map((item) => item.label).join(", "), + responseText: chosenItems + .filter((item) => item.isSelected) + .map((item) => item.label) + .join(", "), }; } else { // User cancelled the prompt, send the cancellation @@ -140,7 +157,9 @@ function onItemsSelected(chosenItems: ICheckboxQuickPickItem[] | undefined): ISh } } -function onItemSelected(chosenItem: vscode.QuickPickItem | undefined): IShowChoicePromptResponseBody { +function onItemSelected( + chosenItem: vscode.QuickPickItem | undefined, +): IShowChoicePromptResponseBody { if (chosenItem !== undefined) { return { promptCancelled: false, @@ -155,7 +174,9 @@ function onItemSelected(chosenItem: vscode.QuickPickItem | undefined): IShowChoi } } -function onInputEntered(responseText: string | undefined): IShowInputPromptResponseBody { +function onInputEntered( + responseText: string | undefined, +): IShowInputPromptResponseBody { if (responseText !== undefined) { return { promptCancelled: false, @@ -176,43 +197,69 @@ export class ConsoleFeature extends LanguageClientConsumer { constructor(private logger: ILogger) { super(); this.commands = [ - vscode.commands.registerCommand("PowerShell.RunSelection", async () => { - if (vscode.window.activeTerminal && - vscode.window.activeTerminal.name !== "PowerShell Extension") { - this.logger.write("PowerShell Extension Terminal is not active! Running in current terminal using 'runSelectedText'."); - await vscode.commands.executeCommand("workbench.action.terminal.runSelectedText"); - - // We need to honor the focusConsoleOnExecute setting here too. However, the boolean that `show` - // takes is called `preserveFocus` which when `true` the terminal will not take focus. - // This is the inverse of focusConsoleOnExecute so we have to inverse the boolean. - vscode.window.activeTerminal.show(!getSettings().integratedConsole.focusConsoleOnExecute); - await vscode.commands.executeCommand("workbench.action.terminal.scrollToBottom"); - - return; - } - - const editor = vscode.window.activeTextEditor; - if (editor === undefined) { - return; - } - - let selectionRange: vscode.Range; - - if (!editor.selection.isEmpty) { - selectionRange = new vscode.Range(editor.selection.start, editor.selection.end); - } else { - selectionRange = editor.document.lineAt(editor.selection.start.line).range; - } - const client = await LanguageClientConsumer.getLanguageClient(); - await client.sendRequest(EvaluateRequestType, { - expression: editor.document.getText(selectionRange), - }); - - // Show the Extension Terminal if it isn't already visible and - // scroll terminal to bottom so new output is visible - await vscode.commands.executeCommand("PowerShell.ShowSessionConsole", true); - await vscode.commands.executeCommand("workbench.action.terminal.scrollToBottom"); - }), + vscode.commands.registerCommand( + "PowerShell.RunSelection", + async () => { + if ( + vscode.window.activeTerminal && + vscode.window.activeTerminal.name !== + "PowerShell Extension" + ) { + this.logger.write( + "PowerShell Extension Terminal is not active! Running in current terminal using 'runSelectedText'.", + ); + await vscode.commands.executeCommand( + "workbench.action.terminal.runSelectedText", + ); + + // We need to honor the focusConsoleOnExecute setting here too. However, the boolean that `show` + // takes is called `preserveFocus` which when `true` the terminal will not take focus. + // This is the inverse of focusConsoleOnExecute so we have to inverse the boolean. + vscode.window.activeTerminal.show( + !getSettings().integratedConsole + .focusConsoleOnExecute, + ); + await vscode.commands.executeCommand( + "workbench.action.terminal.scrollToBottom", + ); + + return; + } + + const editor = vscode.window.activeTextEditor; + if (editor === undefined) { + return; + } + + let selectionRange: vscode.Range; + + if (!editor.selection.isEmpty) { + selectionRange = new vscode.Range( + editor.selection.start, + editor.selection.end, + ); + } else { + selectionRange = editor.document.lineAt( + editor.selection.start.line, + ).range; + } + const client = + await LanguageClientConsumer.getLanguageClient(); + await client.sendRequest(EvaluateRequestType, { + expression: editor.document.getText(selectionRange), + }); + + // Show the Extension Terminal if it isn't already visible and + // scroll terminal to bottom so new output is visible + await vscode.commands.executeCommand( + "PowerShell.ShowSessionConsole", + true, + ); + await vscode.commands.executeCommand( + "workbench.action.terminal.scrollToBottom", + ); + }, + ), ]; } @@ -230,11 +277,13 @@ export class ConsoleFeature extends LanguageClientConsumer { this.handlers = [ languageClient.onRequest( ShowChoicePromptRequestType, - (promptDetails) => showChoicePrompt(promptDetails)), + (promptDetails) => showChoicePrompt(promptDetails), + ), languageClient.onRequest( ShowInputPromptRequestType, - (promptDetails) => showInputPrompt(promptDetails)), + (promptDetails) => showInputPrompt(promptDetails), + ), ]; } } diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index e5dbf4984d..f45e674fb9 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -1,49 +1,51 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import type { DebugProtocol } from "@vscode/debugprotocol"; +import path from "path"; import { - debug, - type CancellationToken, - CancellationTokenSource, - type DebugAdapterDescriptor, - type DebugAdapterDescriptorFactory, - DebugAdapterExecutable, - DebugAdapterNamedPipeServer, - type DebugConfiguration, - type DebugConfigurationProvider, - type DebugSession, - type ExtensionContext, - type WorkspaceFolder, - Disposable, - window, - extensions, - workspace, - commands, - type InputBoxOptions, - type QuickPickItem, - type QuickPickOptions, - DebugConfigurationProviderTriggerKind, - type DebugAdapterTrackerFactory, - type DebugAdapterTracker, - type LogOutputChannel, + CancellationTokenSource, + DebugAdapterExecutable, + DebugAdapterNamedPipeServer, + DebugConfigurationProviderTriggerKind, + Disposable, + commands, + debug, + extensions, + window, + workspace, + type CancellationToken, + type DebugAdapterDescriptor, + type DebugAdapterDescriptorFactory, + type DebugAdapterTracker, + type DebugAdapterTrackerFactory, + type DebugConfiguration, + type DebugConfigurationProvider, + type DebugSession, + type ExtensionContext, + type InputBoxOptions, + type LogOutputChannel, + type QuickPickItem, + type QuickPickOptions, + type WorkspaceFolder, } from "vscode"; -import type { DebugProtocol } from "@vscode/debugprotocol"; import { NotificationType, RequestType } from "vscode-languageclient"; import { LanguageClient } from "vscode-languageclient/node"; import { LanguageClientConsumer } from "../languageClientConsumer"; import type { ILogger } from "../logging"; import { OperatingSystem, getPlatformDetails } from "../platform"; import { PowerShellProcess } from "../process"; -import { type IEditorServicesSessionDetails, SessionManager } from "../session"; +import { SessionManager, type IEditorServicesSessionDetails } from "../session"; import { getSettings } from "../settings"; -import path from "path"; import { checkIfFileExists } from "../utils"; -export const StartDebuggerNotificationType = - new NotificationType("powerShell/startDebugger"); +export const StartDebuggerNotificationType = new NotificationType( + "powerShell/startDebugger", +); -export const StopDebuggerNotificationType = - new NotificationType("powerShell/stopDebugger"); +export const StopDebuggerNotificationType = new NotificationType( + "powerShell/stopDebugger", +); export enum DebugConfig { LaunchCurrentFile, @@ -59,7 +61,10 @@ export enum DebugConfig { /** Make the implicit behavior of undefined and null in the debug api more explicit */ type PREVENT_DEBUG_START = undefined; type PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG = null; -type ResolveDebugConfigurationResult = DebugConfiguration | PREVENT_DEBUG_START | PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; +type ResolveDebugConfigurationResult = + | DebugConfiguration + | PREVENT_DEBUG_START + | PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; const PREVENT_DEBUG_START = undefined; const PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG = null; @@ -78,7 +83,7 @@ export const DebugConfigurations: Record = { name: "PowerShell: Launch Script", type: "PowerShell", request: "launch", - script: "Enter path or command to execute, for example: \"${workspaceFolder}/src/foo.ps1\" or \"Invoke-Pester\"", + script: 'Enter path or command to execute, for example: "${workspaceFolder}/src/foo.ps1" or "Invoke-Pester"', args: [], }, [DebugConfig.InteractiveSession]: { @@ -103,13 +108,13 @@ export const DebugConfigurations: Record = { name: "PowerShell: Module Interactive Session", type: "PowerShell", request: "launch", - script: "Enter command to import your binary module, for example: \"Import-Module -Force ${workspaceFolder}/path/to/module.psd1|dll\"", + script: 'Enter command to import your binary module, for example: "Import-Module -Force ${workspaceFolder}/path/to/module.psd1|dll"', }, [DebugConfig.BinaryModule]: { name: "PowerShell: Binary Module Interactive", type: "PowerShell", request: "launch", - script: "Enter command to import your binary module, for example: \"Import-Module -Force ${workspaceFolder}/path/to/module.psd1|dll\"", + script: 'Enter command to import your binary module, for example: "Import-Module -Force ${workspaceFolder}/path/to/module.psd1|dll"', createTemporaryIntegratedConsole: true, attachDotnetDebugger: true, }, @@ -120,18 +125,24 @@ export const DebugConfigurations: Record = { script: "Invoke-Pester", createTemporaryIntegratedConsole: true, attachDotnetDebugger: true, - } + }, }; -export class DebugSessionFeature extends LanguageClientConsumer - implements DebugConfigurationProvider, DebugAdapterDescriptorFactory { +export class DebugSessionFeature + extends LanguageClientConsumer + implements DebugConfigurationProvider, DebugAdapterDescriptorFactory +{ private tempDebugProcess: PowerShellProcess | undefined; private tempSessionDetails: IEditorServicesSessionDetails | undefined; private commands: Disposable[] = []; private handlers: Disposable[] = []; private adapterName = "PowerShell"; - constructor(context: ExtensionContext, private sessionManager: SessionManager, private logger: ILogger) { + constructor( + context: ExtensionContext, + private sessionManager: SessionManager, + private logger: ILogger, + ) { super(); this.activateDebugAdaptor(context); @@ -140,15 +151,22 @@ export class DebugSessionFeature extends LanguageClientConsumer // substitutions in VS Code debug configurations are required to return // strings. Hence to the `toString()` on these. this.commands = [ - commands.registerCommand("PowerShell.PickPSHostProcess", async () => { - const processId = await this.pickPSHostProcess(); - return processId?.toString(); - }), - - commands.registerCommand("PowerShell.PickRunspace", async (processId) => { - const runspace = await this.pickRunspace(processId); - return runspace?.toString(); - }, this), + commands.registerCommand( + "PowerShell.PickPSHostProcess", + async () => { + const processId = await this.pickPSHostProcess(); + return processId?.toString(); + }, + ), + + commands.registerCommand( + "PowerShell.PickRunspace", + async (processId) => { + const runspace = await this.pickRunspace(processId); + return runspace?.toString(); + }, + this, + ), ]; } @@ -166,19 +184,25 @@ export class DebugSessionFeature extends LanguageClientConsumer public activateDebugAdaptor(context: ExtensionContext): void { const triggers = [ DebugConfigurationProviderTriggerKind.Initial, - DebugConfigurationProviderTriggerKind.Dynamic + DebugConfigurationProviderTriggerKind.Dynamic, ]; - for (const triggerKind of triggers) { context.subscriptions.push( - debug.registerDebugConfigurationProvider(this.adapterName, this, triggerKind) + debug.registerDebugConfigurationProvider( + this.adapterName, + this, + triggerKind, + ), ); } context.subscriptions.push( - debug.registerDebugAdapterTrackerFactory(this.adapterName, new PowerShellDebugAdapterTrackerFactory(this.adapterName)), - debug.registerDebugAdapterDescriptorFactory(this.adapterName, this) + debug.registerDebugAdapterTrackerFactory( + this.adapterName, + new PowerShellDebugAdapterTrackerFactory(this.adapterName), + ), + debug.registerDebugAdapterDescriptorFactory(this.adapterName, this), ); } @@ -186,23 +210,30 @@ export class DebugSessionFeature extends LanguageClientConsumer this.handlers = [ languageClient.onNotification( StartDebuggerNotificationType, - () => void debug.startDebugging(undefined, DebugConfigurations[DebugConfig.InteractiveSession])), + () => + void debug.startDebugging( + undefined, + DebugConfigurations[DebugConfig.InteractiveSession], + ), + ), languageClient.onNotification( StopDebuggerNotificationType, - () => void debug.stopDebugging(undefined)) + () => void debug.stopDebugging(undefined), + ), ]; } public async provideDebugConfigurations( _folder: WorkspaceFolder | undefined, - _token?: CancellationToken): Promise { - + _token?: CancellationToken, + ): Promise { const debugConfigPickItems = [ { id: DebugConfig.LaunchCurrentFile, label: "Launch Current File", - description: "Launch and debug the file in the currently active editor window", + description: + "Launch and debug the file in the currently active editor window", }, { id: DebugConfig.LaunchScript, @@ -212,39 +243,45 @@ export class DebugSessionFeature extends LanguageClientConsumer { id: DebugConfig.InteractiveSession, label: "Interactive Session", - description: "Debug commands executed from the PowerShell Extension Terminal", + description: + "Debug commands executed from the PowerShell Extension Terminal", }, { id: DebugConfig.AttachHostProcess, label: "Attach", - description: "Attach the debugger to a running PowerShell Host Process", + description: + "Attach the debugger to a running PowerShell Host Process", }, { id: DebugConfig.RunPester, label: "Run Pester Tests", - description: "Debug Pester Tests detected in your current directory (runs Invoke-Pester)", + description: + "Debug Pester Tests detected in your current directory (runs Invoke-Pester)", }, { id: DebugConfig.ModuleInteractiveSession, label: "Interactive Session (Module)", - description: "Debug commands executed from the PowerShell Extension Terminal after auto-loading your module", + description: + "Debug commands executed from the PowerShell Extension Terminal after auto-loading your module", }, { id: DebugConfig.BinaryModule, label: "Interactive Session (Binary Module)", - description: "Debug a .NET binary or hybrid module loaded into a PowerShell session. Breakpoints you set in your .NET (C#/F#/VB/etc.) code will be hit upon command execution. You may want to add a compile or watch action as a pre-launch task to this configuration.", + description: + "Debug a .NET binary or hybrid module loaded into a PowerShell session. Breakpoints you set in your .NET (C#/F#/VB/etc.) code will be hit upon command execution. You may want to add a compile or watch action as a pre-launch task to this configuration.", }, { id: DebugConfig.RunPester, label: "Run Pester Tests (Binary Module)", - description: "Debug a .NET binary or hybrid module by running Pester tests. Breakpoints you set in your .NET (C#/F#/VB/etc.) code will be hit upon command execution. You may want to add a compile or watch action as a pre-launch task to this configuration.", + description: + "Debug a .NET binary or hybrid module by running Pester tests. Breakpoints you set in your .NET (C#/F#/VB/etc.) code will be hit upon command execution. You may want to add a compile or watch action as a pre-launch task to this configuration.", }, ]; - const launchSelection = - await window.showQuickPick( - debugConfigPickItems, - { placeHolder: "Select a PowerShell debug configuration" }); + const launchSelection = await window.showQuickPick( + debugConfigPickItems, + { placeHolder: "Select a PowerShell debug configuration" }, + ); if (launchSelection) { return [DebugConfigurations[launchSelection.id]]; @@ -258,21 +295,27 @@ export class DebugSessionFeature extends LanguageClientConsumer public async resolveDebugConfiguration( _folder: WorkspaceFolder | undefined, config: DebugConfiguration, - _token?: CancellationToken): Promise { - + _token?: CancellationToken, + ): Promise { // NOTE: We intentionally do not touch the `cwd` setting of the config. if (!config.request) { // No launch.json, create the default configuration for both unsaved // (Untitled) and saved documents. - const LaunchCurrentFileConfig = DebugConfigurations[DebugConfig.LaunchCurrentFile]; + const LaunchCurrentFileConfig = + DebugConfigurations[DebugConfig.LaunchCurrentFile]; config = { ...config, ...LaunchCurrentFileConfig }; config.current_document = true; } - if (config.script === "${file}" || config.script === "${relativeFile}") { + if ( + config.script === "${file}" || + config.script === "${relativeFile}" + ) { if (window.activeTextEditor === undefined) { - void this.logger.writeAndShowError("To debug the 'Current File', you must first open a PowerShell script file in the editor."); + void this.logger.writeAndShowError( + "To debug the 'Current File', you must first open a PowerShell script file in the editor.", + ); return PREVENT_DEBUG_START; } config.current_document = true; @@ -290,22 +333,25 @@ export class DebugSessionFeature extends LanguageClientConsumer public async resolveDebugConfigurationWithSubstitutedVariables( _folder: WorkspaceFolder | undefined, config: DebugConfiguration, - _token?: CancellationToken): Promise { - + _token?: CancellationToken, + ): Promise { let resolvedConfig: ResolveDebugConfigurationResult; // Prevent the Debug Console from opening config.internalConsoleOptions = "neverOpen"; const settings = getSettings(); - config.createTemporaryIntegratedConsole ??= settings.debugging.createTemporaryIntegratedConsole; + config.createTemporaryIntegratedConsole ??= + settings.debugging.createTemporaryIntegratedConsole; config.executeMode ??= settings.debugging.executeMode; if (config.request === "attach") { resolvedConfig = await this.resolveAttachDebugConfiguration(config); } else if (config.request === "launch") { resolvedConfig = await this.resolveLaunchDebugConfiguration(config); } else { - void this.logger.writeAndShowError(`PowerShell debug configuration's request type was invalid: '${config.request}'.`); + void this.logger.writeAndShowError( + `PowerShell debug configuration's request type was invalid: '${config.request}'.`, + ); return PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; } @@ -320,11 +366,12 @@ export class DebugSessionFeature extends LanguageClientConsumer // make this method async. public async createDebugAdapterDescriptor( session: DebugSession, - _executable: DebugAdapterExecutable | undefined): Promise { - + _executable: DebugAdapterExecutable | undefined, + ): Promise { await this.sessionManager.start(); - const sessionDetails = session.configuration.createTemporaryIntegratedConsole + const sessionDetails = session.configuration + .createTemporaryIntegratedConsole ? await this.createTemporaryIntegratedConsole(session) : this.sessionManager.getSessionDetails(); @@ -335,39 +382,61 @@ export class DebugSessionFeature extends LanguageClientConsumer // Create or show the debug terminal (either temporary or session). this.sessionManager.showDebugTerminal(true); - this.logger.writeDebug(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`); - this.logger.writeDebug(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`); + this.logger.writeDebug( + `Connecting to pipe: ${sessionDetails.debugServicePipeName}`, + ); + this.logger.writeDebug( + `Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`, + ); - return new DebugAdapterNamedPipeServer(sessionDetails.debugServicePipeName); + return new DebugAdapterNamedPipeServer( + sessionDetails.debugServicePipeName, + ); } - private async resolveLaunchDebugConfiguration(config: DebugConfiguration): Promise { + private async resolveLaunchDebugConfiguration( + config: DebugConfiguration, + ): Promise { // Check the languageId and file extension only for current documents // (which includes untitled documents). This prevents accidentally // running the debugger for an open non-PowerShell file. if (config.current_document) { const currentDocument = window.activeTextEditor?.document; if (currentDocument?.languageId !== "powershell") { - void this.logger.writeAndShowError(`PowerShell does not support debugging this language mode: '${currentDocument?.languageId}'.`); + void this.logger.writeAndShowError( + `PowerShell does not support debugging this language mode: '${currentDocument?.languageId}'.`, + ); return PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; } if (await checkIfFileExists(config.script)) { const ext = path.extname(config.script).toLowerCase(); if (!(ext === ".ps1" || ext === ".psm1")) { - void this.logger.writeAndShowError(`PowerShell does not support debugging this file type: '${path.basename(config.script)}'.`); + void this.logger.writeAndShowError( + `PowerShell does not support debugging this file type: '${path.basename(config.script)}'.`, + ); return PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; } } } - if (config.untitled_document && config.createTemporaryIntegratedConsole) { - void this.logger.writeAndShowError("PowerShell does not support debugging untitled files in a temporary console."); + if ( + config.untitled_document && + config.createTemporaryIntegratedConsole + ) { + void this.logger.writeAndShowError( + "PowerShell does not support debugging untitled files in a temporary console.", + ); return PREVENT_DEBUG_START; } - if (!config.createTemporaryIntegratedConsole && config.attachDotnetDebugger) { - void this.logger.writeAndShowError("dotnet debugging without using a temporary console is currently not supported. Please updated your launch config to include createTemporaryIntegratedConsole: true."); + if ( + !config.createTemporaryIntegratedConsole && + config.attachDotnetDebugger + ) { + void this.logger.writeAndShowError( + "dotnet debugging without using a temporary console is currently not supported. Please updated your launch config to include createTemporaryIntegratedConsole: true.", + ); return PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; } @@ -378,16 +447,24 @@ export class DebugSessionFeature extends LanguageClientConsumer return config; } - private resolveAttachDotnetDebugConfiguration(config: DebugConfiguration): ResolveDebugConfigurationResult { + private resolveAttachDotnetDebugConfiguration( + config: DebugConfiguration, + ): ResolveDebugConfigurationResult { if (!extensions.getExtension("ms-dotnettools.csharp")) { - void this.logger.writeAndShowError("You specified attachDotnetDebugger in your PowerShell Launch configuration but the C# extension is not installed. Please install the C# extension and try again."); + void this.logger.writeAndShowError( + "You specified attachDotnetDebugger in your PowerShell Launch configuration but the C# extension is not installed. Please install the C# extension and try again.", + ); return PREVENT_DEBUG_START; } - const dotnetDebuggerConfig = this.getDotnetNamedConfigOrDefault(config.dotnetDebuggerConfigName); + const dotnetDebuggerConfig = this.getDotnetNamedConfigOrDefault( + config.dotnetDebuggerConfigName, + ); if (dotnetDebuggerConfig === undefined) { - void this.logger.writeAndShowError(`You specified dotnetDebuggerConfigName in your PowerShell Launch configuration but a matching launch config was not found. Please ensure you have a coreclr attach config with the name ${config.dotnetDebuggerConfigName} in your launch.json file or remove dotnetDebuggerConfigName from your PowerShell Launch configuration to use the defaults`); + void this.logger.writeAndShowError( + `You specified dotnetDebuggerConfigName in your PowerShell Launch configuration but a matching launch config was not found. Please ensure you have a coreclr attach config with the name ${config.dotnetDebuggerConfigName} in your launch.json file or remove dotnetDebuggerConfigName from your PowerShell Launch configuration to use the defaults`, + ); return PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; } @@ -395,12 +472,17 @@ export class DebugSessionFeature extends LanguageClientConsumer return config; } - private async createTemporaryIntegratedConsole(session: DebugSession): Promise { + private async createTemporaryIntegratedConsole( + session: DebugSession, + ): Promise { const settings = getSettings(); - this.tempDebugProcess = await this.sessionManager.createDebugSessionProcess(settings); + this.tempDebugProcess = + await this.sessionManager.createDebugSessionProcess(settings); // TODO: Maybe set a timeout on the cancellation token? const cancellationTokenSource = new CancellationTokenSource(); - this.tempSessionDetails = await this.tempDebugProcess.start(cancellationTokenSource.token); + this.tempSessionDetails = await this.tempDebugProcess.start( + cancellationTokenSource.token, + ); // NOTE: Dotnet attach debugging is only currently supported if a temporary debug terminal is used, otherwise we get lots of lock conflicts from loading the assemblies. if (session.configuration.attachDotnetDebugger) { @@ -409,73 +491,103 @@ export class DebugSessionFeature extends LanguageClientConsumer // Will wait until the process is started and available before attaching const pid = await this.tempDebugProcess.getPid(); if (pid === undefined) { - void this.logger.writeAndShowError("Attach Dotnet Debugger was specified but the PowerShell temporary debug session failed to start. This is probably a bug."); + void this.logger.writeAndShowError( + "Attach Dotnet Debugger was specified but the PowerShell temporary debug session failed to start. This is probably a bug.", + ); return PREVENT_DEBUG_START; } dotnetAttachConfig.processId = pid; // Ensure the .NET session stops before the PowerShell session so that the .NET debug session doesn't emit an error about the process unexpectedly terminating. let tempConsoleDotnetAttachSession: DebugSession; - const startDebugEvent = debug.onDidStartDebugSession(dotnetAttachSession => { - if (dotnetAttachSession.configuration.name != dotnetAttachConfig.name) { return; } - - // Makes the event one-time - // HACK: This seems like you would be calling a method on a variable not assigned yet, but it does work in the flow. - // The dispose shorthand demonry for making an event one-time courtesy of: https://github.com/OmniSharp/omnisharp-vscode/blob/b8b07bb12557b4400198895f82a94895cb90c461/test/integrationTests/launchConfiguration.integration.test.ts#L41-L45 - startDebugEvent.dispose(); - - this.logger.writeDebug(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`); - - tempConsoleDotnetAttachSession = dotnetAttachSession; - - const stopDebugEvent = debug.onDidTerminateDebugSession(async tempConsoleSession => { - if (tempConsoleDotnetAttachSession.parentSession?.id !== tempConsoleSession.id) { return; } + const startDebugEvent = debug.onDidStartDebugSession( + (dotnetAttachSession) => { + if ( + dotnetAttachSession.configuration.name != + dotnetAttachConfig.name + ) { + return; + } // Makes the event one-time - stopDebugEvent.dispose(); - - this.logger.writeDebug(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`); - - // HACK: As of 2023-08-17, there is no vscode debug API to request the C# debugger to detach, so we send it a custom DAP request instead. - const disconnectRequest: DebugProtocol.DisconnectRequest = { - command: "disconnect", - seq: 0, - type: "request", - arguments: { - restart: false, - terminateDebuggee: false, - suspendDebuggee: false - } - }; - - try { - await dotnetAttachSession.customRequest( - disconnectRequest.command, - disconnectRequest.arguments - ); - } catch (err) { - this.logger.writeWarning(`Disconnect request to dotnet debugger failed: ${err}`); - } - }); - }); + // HACK: This seems like you would be calling a method on a variable not assigned yet, but it does work in the flow. + // The dispose shorthand demonry for making an event one-time courtesy of: https://github.com/OmniSharp/omnisharp-vscode/blob/b8b07bb12557b4400198895f82a94895cb90c461/test/integrationTests/launchConfiguration.integration.test.ts#L41-L45 + startDebugEvent.dispose(); + + this.logger.writeDebug( + `Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`, + ); + + tempConsoleDotnetAttachSession = dotnetAttachSession; + + const stopDebugEvent = debug.onDidTerminateDebugSession( + async (tempConsoleSession) => { + if ( + tempConsoleDotnetAttachSession.parentSession + ?.id !== tempConsoleSession.id + ) { + return; + } + + // Makes the event one-time + stopDebugEvent.dispose(); + + this.logger.writeDebug( + `Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`, + ); + + // HACK: As of 2023-08-17, there is no vscode debug API to request the C# debugger to detach, so we send it a custom DAP request instead. + const disconnectRequest: DebugProtocol.DisconnectRequest = + { + command: "disconnect", + seq: 0, + type: "request", + arguments: { + restart: false, + terminateDebuggee: false, + suspendDebuggee: false, + }, + }; + + try { + await dotnetAttachSession.customRequest( + disconnectRequest.command, + disconnectRequest.arguments, + ); + } catch (err) { + this.logger.writeWarning( + `Disconnect request to dotnet debugger failed: ${err}`, + ); + } + }, + ); + }, + ); // Start a child debug session to attach the dotnet debugger // TODO: Accommodate multi-folder workspaces if the C# code is in a different workspace folder await debug.startDebugging(undefined, dotnetAttachConfig, session); - this.logger.writeDebug(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`); - this.logger.writeDebug(`Attached dotnet debugger to process: ${pid}`); + this.logger.writeDebug( + `Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`, + ); + this.logger.writeDebug( + `Attached dotnet debugger to process: ${pid}`, + ); } return this.tempSessionDetails; } - private getDotnetNamedConfigOrDefault(configName?: string): ResolveDebugConfigurationResult { + private getDotnetNamedConfigOrDefault( + configName?: string, + ): ResolveDebugConfigurationResult { if (configName) { const debugConfigs = this.getLaunchConfigurations(); - return debugConfigs.find(({ type, request, name }) => - type === "coreclr" && - request === "attach" && - name === configName + return debugConfigs.find( + ({ type, request, name }) => + type === "coreclr" && + request === "attach" && + name === configName, ); } @@ -487,27 +599,41 @@ export class DebugSessionFeature extends LanguageClientConsumer request: "attach", processId: undefined, logging: { - moduleLoad: false - } + moduleLoad: false, + }, }; } /** Fetches all available vscode launch configurations. This is abstracted out for easier testing. */ private getLaunchConfigurations(): DebugConfiguration[] { - return workspace.getConfiguration("launch").get("configurations") ?? []; + return ( + workspace + .getConfiguration("launch") + .get("configurations") ?? [] + ); } - private async resolveAttachDebugConfiguration(config: DebugConfiguration): Promise { + private async resolveAttachDebugConfiguration( + config: DebugConfiguration, + ): Promise { const platformDetails = getPlatformDetails(); - const versionDetails = this.sessionManager.getPowerShellVersionDetails(); + const versionDetails = + this.sessionManager.getPowerShellVersionDetails(); if (versionDetails === undefined) { - void this.logger.writeAndShowError(`PowerShell session version details were not found for '${config.name}'.`); + void this.logger.writeAndShowError( + `PowerShell session version details were not found for '${config.name}'.`, + ); return PREVENT_DEBUG_START; } // Cross-platform attach to process was added in 6.2.0-preview.4. - if (versionDetails.version < "7.0.0" && platformDetails.operatingSystem !== OperatingSystem.Windows) { - void this.logger.writeAndShowError(`Attaching to a PowerShell Host Process on ${OperatingSystem[platformDetails.operatingSystem]} requires PowerShell 7.0 or higher (Current Version: ${versionDetails.version}).`); + if ( + versionDetails.version < "7.0.0" && + platformDetails.operatingSystem !== OperatingSystem.Windows + ) { + void this.logger.writeAndShowError( + `Attaching to a PowerShell Host Process on ${OperatingSystem[platformDetails.operatingSystem]} requires PowerShell 7.0 or higher (Current Version: ${versionDetails.version}).`, + ); return PREVENT_DEBUG_START; } @@ -522,18 +648,27 @@ export class DebugSessionFeature extends LanguageClientConsumer // If we were given a stringified int from the user, or from the picker // command, we need to parse it here. - if (typeof config.processId === "string" && config.processId != "current") { + if ( + typeof config.processId === "string" && + config.processId != "current" + ) { config.processId = parseInt(config.processId); } // NOTE: We don't support attaching to the Extension Terminal, even // though in the past it looked like we did. The implementation was // half-baked and left things unusable. - if (config.processId === "current" || config.processId === await this.sessionManager.getLanguageServerPid()) { + if ( + config.processId === "current" || + config.processId === + (await this.sessionManager.getLanguageServerPid()) + ) { // TODO: When (if ever) it's supported, we need to convert 0 and the // old notion of "current" to the actual process ID, like this: // config.processId = await this.sessionManager.getLanguageServerPid(); - void this.logger.writeAndShowError("Attaching to the PowerShell Extension terminal is not supported. Please use the 'PowerShell: Interactive Session' debug configuration instead."); + void this.logger.writeAndShowError( + "Attaching to the PowerShell Extension terminal is not supported. Please use the 'PowerShell: Interactive Session' debug configuration instead.", + ); return PREVENT_DEBUG_START_AND_OPEN_DEBUGCONFIG; } @@ -550,7 +685,10 @@ export class DebugSessionFeature extends LanguageClientConsumer private async pickPSHostProcess(): Promise { const client = await LanguageClientConsumer.getLanguageClient(); - const response = await client.sendRequest(GetPSHostProcessesRequestType, {}); + const response = await client.sendRequest( + GetPSHostProcessesRequestType, + {}, + ); const items: IProcessItem[] = []; for (const process of response) { let windowTitle = ""; @@ -566,7 +704,9 @@ export class DebugSessionFeature extends LanguageClientConsumer } if (items.length === 0) { - return Promise.reject(new Error("There are no PowerShell host processes to attach.")); + return Promise.reject( + new Error("There are no PowerShell host processes to attach."), + ); } const options: QuickPickOptions = { @@ -582,7 +722,9 @@ export class DebugSessionFeature extends LanguageClientConsumer private async pickRunspace(processId: number): Promise { const client = await LanguageClientConsumer.getLanguageClient(); - const response = await client.sendRequest(GetRunspaceRequestType, { processId }); + const response = await client.sendRequest(GetRunspaceRequestType, { + processId, + }); const items: IRunspaceItem[] = []; for (const runspace of response) { items.push({ @@ -604,23 +746,32 @@ export class DebugSessionFeature extends LanguageClientConsumer } } -class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory, Disposable { +class PowerShellDebugAdapterTrackerFactory + implements DebugAdapterTrackerFactory, Disposable +{ disposables: Disposable[] = []; constructor(private adapterName = "PowerShell") {} - _log: LogOutputChannel | undefined; /** Lazily creates a {@link LogOutputChannel} for debug tracing, and presents it only when DAP logging is enabled. - * - * We want to use a shared output log for separate debug sessions as usually only one is running at a time and we - * dont need an output window for every debug session. We also want to leave it active so user can copy and paste - * even on run end. When user changes the setting and disables it getter will return undefined, which will result - * in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the - * user re-enables, then logging resumes. - */ + * + * We want to use a shared output log for separate debug sessions as usually only one is running at a time and we + * dont need an output window for every debug session. We also want to leave it active so user can copy and paste + * even on run end. When user changes the setting and disables it getter will return undefined, which will result + * in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the + * user re-enables, then logging resumes. + */ get log(): LogOutputChannel | undefined { - if (workspace.getConfiguration("powershell.developer").get("traceDap") && this._log === undefined) { - this._log = window.createOutputChannel(`${this.adapterName}: Trace DAP`, { log: true }); + if ( + workspace + .getConfiguration("powershell.developer") + .get("traceDap") && + this._log === undefined + ) { + this._log = window.createOutputChannel( + `${this.adapterName}: Trace DAP`, + { log: true }, + ); this.disposables.push(this._log); } return this._log; @@ -630,32 +781,50 @@ class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory createDebugAdapterTracker(session: DebugSession): DebugAdapterTracker { const sessionInfo = `${this.adapterName} Debug Session: ${session.name} [${session.id}]`; return { - onWillStartSession: () => this.log?.info(`Starting ${sessionInfo}. Set log level to trace to see DAP messages beyond errors`), + onWillStartSession: () => + this.log?.info( + `Starting ${sessionInfo}. Set log level to trace to see DAP messages beyond errors`, + ), onWillStopSession: () => this.log?.info(`Stopping ${sessionInfo}`), - onExit: code => this.log?.info(`${sessionInfo} exited with code ${code}`), + onExit: (code) => + this.log?.info(`${sessionInfo} exited with code ${code}`), onWillReceiveMessage: (m): void => { this.log?.debug(`➡️${m.seq} ${m.type}: ${m.command}`); - if (m.arguments && (Array.isArray(m.arguments) ? m.arguments.length > 0 : Object.keys(m.arguments).length > 0)) { - this.log?.trace(`${m.seq}: ` + JSON.stringify(m.arguments, undefined, 2)); + if ( + m.arguments && + (Array.isArray(m.arguments) + ? m.arguments.length > 0 + : Object.keys(m.arguments).length > 0) + ) { + this.log?.trace( + `${m.seq}: ` + + JSON.stringify(m.arguments, undefined, 2), + ); } }, - onDidSendMessage: (m):void => { - const responseSummary = m.request_seq !== undefined - ? `${m.success ? "✅" : "❌"}${m.request_seq} ${m.type}(${m.seq}): ${m.command}` - : `⬅️${m.seq} ${m.type}: ${m.event ?? m.command}`; - this.log?.debug( - responseSummary - ); - if (m.body && (Array.isArray(m.body) ? m.body.length > 0 : Object.keys(m.body).length > 0)) { - this.log?.trace(`${m.seq}: ` + JSON.stringify(m.body, undefined, 2)); + onDidSendMessage: (m): void => { + const responseSummary = + m.request_seq !== undefined + ? `${m.success ? "✅" : "❌"}${m.request_seq} ${m.type}(${m.seq}): ${m.command}` + : `⬅️${m.seq} ${m.type}: ${m.event ?? m.command}`; + this.log?.debug(responseSummary); + if ( + m.body && + (Array.isArray(m.body) + ? m.body.length > 0 + : Object.keys(m.body).length > 0) + ) { + this.log?.trace( + `${m.seq}: ` + JSON.stringify(m.body, undefined, 2), + ); } }, - onError: e => this.log?.error(e), + onError: (e) => this.log?.error(e), }; } dispose(): void { - this.disposables.forEach(d => d.dispose()); + this.disposables.forEach((d) => d.dispose()); } } @@ -666,9 +835,12 @@ export class SpecifyScriptArgsFeature implements Disposable { constructor(context: ExtensionContext) { this.context = context; - this.command = commands.registerCommand("PowerShell.SpecifyScriptArgs", () => { - return this.specifyScriptArguments(); - }); + this.command = commands.registerCommand( + "PowerShell.SpecifyScriptArgs", + () => { + return this.specifyScriptArguments(); + }, + ); } public dispose(): void { @@ -680,10 +852,14 @@ export class SpecifyScriptArgsFeature implements Disposable { const options: InputBoxOptions = { ignoreFocusOut: true, - placeHolder: "Enter script arguments or leave empty to pass no args", + placeHolder: + "Enter script arguments or leave empty to pass no args", }; - const prevArgs = this.context.workspaceState.get(powerShellDbgScriptArgsKey, ""); + const prevArgs = this.context.workspaceState.get( + powerShellDbgScriptArgsKey, + "", + ); if (prevArgs.length > 0) { options.value = prevArgs; } @@ -692,7 +868,10 @@ export class SpecifyScriptArgsFeature implements Disposable { // When user cancel's the input box (by pressing Esc), the text value is undefined. // Let's not blow away the previous setting. if (text !== undefined) { - await this.context.workspaceState.update(powerShellDbgScriptArgsKey, text); + await this.context.workspaceState.update( + powerShellDbgScriptArgsKey, + text, + ); } return text; @@ -703,8 +882,7 @@ interface IProcessItem extends QuickPickItem { processId: number; // payload for the QuickPick UI } -interface IGetPSHostProcessesArguments { -} +interface IGetPSHostProcessesArguments {} interface IPSHostProcessInfo { processName: string; @@ -713,16 +891,17 @@ interface IPSHostProcessInfo { mainWindowTitle: string; } -export const GetPSHostProcessesRequestType = - new RequestType("powerShell/getPSHostProcesses"); - +export const GetPSHostProcessesRequestType = new RequestType< + IGetPSHostProcessesArguments, + IPSHostProcessInfo[], + string +>("powerShell/getPSHostProcesses"); interface IRunspaceItem extends QuickPickItem { id: number; // payload for the QuickPick UI } -interface IGetRunspaceRequestArguments { -} +interface IGetRunspaceRequestArguments {} interface IRunspace { id: number; @@ -730,5 +909,8 @@ interface IRunspace { availability: string; } -export const GetRunspaceRequestType = - new RequestType("powerShell/getRunspace"); +export const GetRunspaceRequestType = new RequestType< + IGetRunspaceRequestArguments, + IRunspace[], + string +>("powerShell/getRunspace"); diff --git a/src/features/Examples.ts b/src/features/Examples.ts index 8dcddbeb79..d3c0b0da56 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import path = require("path"); -import utils = require("../utils") +import utils = require("../utils"); import vscode = require("vscode"); export class ExamplesFeature implements vscode.Disposable { @@ -10,13 +10,22 @@ export class ExamplesFeature implements vscode.Disposable { private examplesPath: vscode.Uri; constructor() { - this.examplesPath = vscode.Uri.file(path.resolve(__dirname, "../examples")); - this.command = vscode.commands.registerCommand("PowerShell.OpenExamplesFolder", async () => { - await vscode.commands.executeCommand("vscode.openFolder", this.examplesPath, true); - // Return existence of the path for testing. The `vscode.openFolder` - // command should do this, but doesn't (yet). - return utils.checkIfFileExists(this.examplesPath); - }); + this.examplesPath = vscode.Uri.file( + path.resolve(__dirname, "../examples"), + ); + this.command = vscode.commands.registerCommand( + "PowerShell.OpenExamplesFolder", + async () => { + await vscode.commands.executeCommand( + "vscode.openFolder", + this.examplesPath, + true, + ); + // Return existence of the path for testing. The `vscode.openFolder` + // command should do this, but doesn't (yet). + return utils.checkIfFileExists(this.examplesPath); + }, + ); } public dispose(): void { diff --git a/src/features/ExpandAlias.ts b/src/features/ExpandAlias.ts index 652be565c3..45e86be7e1 100644 --- a/src/features/ExpandAlias.ts +++ b/src/features/ExpandAlias.ts @@ -3,55 +3,76 @@ import vscode = require("vscode"); import { RequestType } from "vscode-languageclient"; -import { LanguageClientConsumer } from "../languageClientConsumer"; import type { LanguageClient } from "vscode-languageclient/node"; +import { LanguageClientConsumer } from "../languageClientConsumer"; -interface IExpandAliasRequestArguments { -} +interface IExpandAliasRequestArguments {} interface IExpandAliasRequestResponse { - text: string + text: string; } -export const ExpandAliasRequestType = new RequestType("powerShell/expandAlias"); +export const ExpandAliasRequestType = new RequestType< + IExpandAliasRequestArguments, + IExpandAliasRequestResponse, + void +>("powerShell/expandAlias"); export class ExpandAliasFeature extends LanguageClientConsumer { private command: vscode.Disposable; constructor() { super(); - this.command = vscode.commands.registerCommand("PowerShell.ExpandAlias", async () => { - const editor = vscode.window.activeTextEditor; - if (editor === undefined) { - return; - } - - const document = editor.document; - const selection = editor.selection; - const sls = selection.start; - const sle = selection.end; - - let text: string; - let range: vscode.Range | vscode.Position; - - if ((sls.character === sle.character) && (sls.line === sle.line)) { - text = document.getText(); - range = new vscode.Range(0, 0, document.lineCount, text.length); - } else { - text = document.getText(selection); - range = new vscode.Range(sls.line, sls.character, sle.line, sle.character); - } - - const client = await LanguageClientConsumer.getLanguageClient(); - const result = await client.sendRequest(ExpandAliasRequestType, { text }); - await editor.edit((editBuilder) => { - editBuilder.replace(range, result.text); - }); - }); + this.command = vscode.commands.registerCommand( + "PowerShell.ExpandAlias", + async () => { + const editor = vscode.window.activeTextEditor; + if (editor === undefined) { + return; + } + + const document = editor.document; + const selection = editor.selection; + const sls = selection.start; + const sle = selection.end; + + let text: string; + let range: vscode.Range | vscode.Position; + + if (sls.character === sle.character && sls.line === sle.line) { + text = document.getText(); + range = new vscode.Range( + 0, + 0, + document.lineCount, + text.length, + ); + } else { + text = document.getText(selection); + range = new vscode.Range( + sls.line, + sls.character, + sle.line, + sle.character, + ); + } + + const client = await LanguageClientConsumer.getLanguageClient(); + const result = await client.sendRequest( + ExpandAliasRequestType, + { text }, + ); + await editor.edit((editBuilder) => { + editBuilder.replace(range, result.text); + }); + }, + ); } - // eslint-disable-next-line @typescript-eslint/no-empty-function - public override onLanguageClientSet(_languageClient: LanguageClient): void {} + public override onLanguageClientSet( + _languageClient: LanguageClient, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): void {} public dispose(): void { this.command.dispose(); diff --git a/src/features/ExtensionCommands.ts b/src/features/ExtensionCommands.ts index 3f445199d4..c4ca7203b0 100644 --- a/src/features/ExtensionCommands.ts +++ b/src/features/ExtensionCommands.ts @@ -4,13 +4,16 @@ import * as path from "path"; import * as vscode from "vscode"; import { - NotificationType, NotificationType0, - Position, Range, RequestType + NotificationType, + NotificationType0, + Position, + Range, + RequestType, } from "vscode-languageclient"; import { LanguageClient } from "vscode-languageclient/node"; +import { LanguageClientConsumer } from "../languageClientConsumer"; import type { ILogger } from "../logging"; import { getSettings, validateCwdSetting } from "../settings"; -import { LanguageClientConsumer } from "../languageClientConsumer"; import { DebugConfig, DebugConfigurations } from "./DebugSession"; export interface IExtensionCommand { @@ -22,9 +25,11 @@ export interface IExtensionCommandQuickPickItem extends vscode.QuickPickItem { command: IExtensionCommand; } -export const InvokeExtensionCommandRequestType = - new RequestType( - "powerShell/invokeExtensionCommand"); +export const InvokeExtensionCommandRequestType = new RequestType< + IInvokeExtensionCommandRequestArguments, + void, + void +>("powerShell/invokeExtensionCommand"); export interface IEditorContext { currentFileContent: string; @@ -41,7 +46,8 @@ export interface IInvokeExtensionCommandRequestArguments { export const ExtensionCommandAddedNotificationType = new NotificationType( - "powerShell/extensionCommandAdded"); + "powerShell/extensionCommandAdded", + ); export interface IExtensionCommandAddedNotificationBody { name: string; @@ -60,23 +66,26 @@ function asCodePosition(value: Position): vscode.Position { return new vscode.Position(value.line, value.character); } -export const GetEditorContextRequestType = - new RequestType( - "editor/getEditorContext"); +export const GetEditorContextRequestType = new RequestType< + IGetEditorContextRequestArguments, + IEditorContext, + void +>("editor/getEditorContext"); -export interface IGetEditorContextRequestArguments { -} +export interface IGetEditorContextRequestArguments {} // NOTE: The server at least now expects this response, but it's not used in any // way. In the future we could actually communicate an error to the user. enum EditorOperationResponse { Completed, - Failed + Failed, } -export const InsertTextRequestType = - new RequestType( - "editor/insertText"); +export const InsertTextRequestType = new RequestType< + IInsertTextRequestArguments, + EditorOperationResponse, + void +>("editor/insertText"); export interface IInsertTextRequestArguments { filePath: string; @@ -84,53 +93,72 @@ export interface IInsertTextRequestArguments { insertRange: Range; } -export const SetSelectionRequestType = - new RequestType( - "editor/setSelection"); +export const SetSelectionRequestType = new RequestType< + ISetSelectionRequestArguments, + EditorOperationResponse, + void +>("editor/setSelection"); export interface ISetSelectionRequestArguments { selectionRange: Range; } -export const OpenFileRequestType = - new RequestType( - "editor/openFile"); +export const OpenFileRequestType = new RequestType< + IOpenFileDetails, + EditorOperationResponse, + void +>("editor/openFile"); export interface IOpenFileDetails { filePath: string; preview: boolean; } -export const NewFileRequestType = - new RequestType( - "editor/newFile"); - -export const CloseFileRequestType = - new RequestType( - "editor/closeFile"); - -export const SaveFileRequestType = - new RequestType( - "editor/saveFile"); - -export const ShowErrorMessageRequestType = - new RequestType( - "editor/showErrorMessage"); - -export const ShowWarningMessageRequestType = - new RequestType( - "editor/showWarningMessage"); - -export const ShowInformationMessageRequestType = - new RequestType( - "editor/showInformationMessage"); - -export const SetStatusBarMessageRequestType = - new RequestType( - "editor/setStatusBarMessage"); - -export const ClearTerminalNotificationType = - new NotificationType0("editor/clearTerminal"); +export const NewFileRequestType = new RequestType< + string, + EditorOperationResponse, + void +>("editor/newFile"); + +export const CloseFileRequestType = new RequestType< + string, + EditorOperationResponse, + void +>("editor/closeFile"); + +export const SaveFileRequestType = new RequestType< + ISaveFileDetails, + EditorOperationResponse, + void +>("editor/saveFile"); + +export const ShowErrorMessageRequestType = new RequestType< + string, + EditorOperationResponse, + void +>("editor/showErrorMessage"); + +export const ShowWarningMessageRequestType = new RequestType< + string, + EditorOperationResponse, + void +>("editor/showWarningMessage"); + +export const ShowInformationMessageRequestType = new RequestType< + string, + EditorOperationResponse, + void +>("editor/showInformationMessage"); + +export const SetStatusBarMessageRequestType = new RequestType< + IStatusBarMessageDetails, + EditorOperationResponse, + void +>("editor/setStatusBarMessage"); + +export const ClearTerminalNotificationType = new NotificationType0( + "editor/clearTerminal", +); export interface ISaveFileDetails { filePath: string; @@ -155,40 +183,70 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { constructor(private logger: ILogger) { super(); this.commands = [ - vscode.commands.registerCommand("PowerShell.ShowAdditionalCommands", async () => { - await this.showExtensionCommands(); - }), + vscode.commands.registerCommand( + "PowerShell.ShowAdditionalCommands", + async () => { + await this.showExtensionCommands(); + }, + ), - vscode.commands.registerCommand("PowerShell.InvokeRegisteredEditorCommand", + vscode.commands.registerCommand( + "PowerShell.InvokeRegisteredEditorCommand", async (param: IInvokeRegisteredEditorCommandParameter) => { const commandToExecute = this.extensionCommands.find( - (x) => x.name === param.commandName); + (x) => x.name === param.commandName, + ); if (commandToExecute) { - - const client = await LanguageClientConsumer.getLanguageClient(); + const client = + await LanguageClientConsumer.getLanguageClient(); await client.sendRequest( InvokeExtensionCommandRequestType, { name: commandToExecute.name, - context: this.getEditorContext() - }); + context: this.getEditorContext(), + }, + ); } - }), - - vscode.commands.registerCommand("PowerShell.ClosePanel", - async () => { await vscode.commands.executeCommand("workbench.action.togglePanel"); }), - - vscode.commands.registerCommand("PowerShell.PositionPanelLeft", - async () => { await vscode.commands.executeCommand("workbench.action.positionPanelLeft"); }), + }, + ), - vscode.commands.registerCommand("PowerShell.PositionPanelBottom", - async () => { await vscode.commands.executeCommand("workbench.action.positionPanelBottom"); }), - - vscode.commands.registerCommand("PowerShell.Debug.Start", + vscode.commands.registerCommand( + "PowerShell.ClosePanel", + async () => { + await vscode.commands.executeCommand( + "workbench.action.togglePanel", + ); + }, + ), + + vscode.commands.registerCommand( + "PowerShell.PositionPanelLeft", + async () => { + await vscode.commands.executeCommand( + "workbench.action.positionPanelLeft", + ); + }, + ), + + vscode.commands.registerCommand( + "PowerShell.PositionPanelBottom", async () => { - await vscode.debug.startDebugging(undefined, DebugConfigurations[DebugConfig.LaunchCurrentFile]); - }) + await vscode.commands.executeCommand( + "workbench.action.positionPanelBottom", + ); + }, + ), + + vscode.commands.registerCommand( + "PowerShell.Debug.Start", + async () => { + await vscode.debug.startDebugging( + undefined, + DebugConfigurations[DebugConfig.LaunchCurrentFile], + ); + }, + ), ]; } @@ -199,61 +257,68 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { this.handlers = [ languageClient.onNotification( ExtensionCommandAddedNotificationType, - (command) => { this.addExtensionCommand(command); }), + (command) => { + this.addExtensionCommand(command); + }, + ), - languageClient.onRequest( - GetEditorContextRequestType, - (_details) => this.getEditorContext()), + languageClient.onRequest(GetEditorContextRequestType, (_details) => + this.getEditorContext(), + ), - languageClient.onRequest( - InsertTextRequestType, - (details) => this.insertText(details)), + languageClient.onRequest(InsertTextRequestType, (details) => + this.insertText(details), + ), - languageClient.onRequest( - SetSelectionRequestType, - (details) => this.setSelection(details)), + languageClient.onRequest(SetSelectionRequestType, (details) => + this.setSelection(details), + ), - languageClient.onRequest( - NewFileRequestType, - (_content) => this.newFile(_content)), + languageClient.onRequest(NewFileRequestType, (_content) => + this.newFile(_content), + ), - languageClient.onRequest( - OpenFileRequestType, - (filePath) => this.openFile(filePath)), + languageClient.onRequest(OpenFileRequestType, (filePath) => + this.openFile(filePath), + ), - languageClient.onRequest( - CloseFileRequestType, - (filePath) => this.closeFile(filePath)), + languageClient.onRequest(CloseFileRequestType, (filePath) => + this.closeFile(filePath), + ), - languageClient.onRequest( - SaveFileRequestType, - (saveFileDetails) => this.saveFile(saveFileDetails)), + languageClient.onRequest(SaveFileRequestType, (saveFileDetails) => + this.saveFile(saveFileDetails), + ), languageClient.onRequest( ShowInformationMessageRequestType, - (message) => this.showInformationMessage(message)), + (message) => this.showInformationMessage(message), + ), - languageClient.onRequest( - ShowErrorMessageRequestType, - (message) => this.showErrorMessage(message)), + languageClient.onRequest(ShowErrorMessageRequestType, (message) => + this.showErrorMessage(message), + ), - languageClient.onRequest( - ShowWarningMessageRequestType, - (message) => this.showWarningMessage(message)), + languageClient.onRequest(ShowWarningMessageRequestType, (message) => + this.showWarningMessage(message), + ), languageClient.onRequest( SetStatusBarMessageRequestType, - (messageDetails) => this.setStatusBarMessage(messageDetails)), - - languageClient.onNotification( - ClearTerminalNotificationType, - () => { - // We check to see if they have TrueClear on. If not, no-op because the - // overriden Clear-Host already calls [System.Console]::Clear() - if (getSettings().integratedConsole.forceClearScrollbackBuffer) { - void vscode.commands.executeCommand("workbench.action.terminal.clear"); - } - }) + (messageDetails) => this.setStatusBarMessage(messageDetails), + ), + + languageClient.onNotification(ClearTerminalNotificationType, () => { + // We check to see if they have TrueClear on. If not, no-op because the + // overriden Clear-Host already calls [System.Console]::Clear() + if ( + getSettings().integratedConsole.forceClearScrollbackBuffer + ) { + void vscode.commands.executeCommand( + "workbench.action.terminal.clear", + ); + } + }), ]; } @@ -269,7 +334,9 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { } } - private addExtensionCommand(command: IExtensionCommandAddedNotificationBody): void { + private addExtensionCommand( + command: IExtensionCommandAddedNotificationBody, + ): void { this.extensionCommands.push({ name: command.name, displayName: command.displayName, @@ -277,59 +344,66 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { this.extensionCommands.sort( (a: IExtensionCommand, b: IExtensionCommand) => - a.name.localeCompare(b.name)); + a.name.localeCompare(b.name), + ); } private async showExtensionCommands(): Promise { // If no extension commands are available, show a message if (this.extensionCommands.length === 0) { - void this.logger.writeAndShowInformation("No extension commands have been loaded into the current session."); + void this.logger.writeAndShowInformation( + "No extension commands have been loaded into the current session.", + ); return; } const quickPickItems = - this.extensionCommands.map((command) => { - return { - label: command.displayName, - description: command.name, - command, - }; - }); + this.extensionCommands.map( + (command) => { + return { + label: command.displayName, + description: command.name, + command, + }; + }, + ); const selectedCommand = await vscode.window.showQuickPick( quickPickItems, - { placeHolder: "Select a command..." }); + { placeHolder: "Select a command..." }, + ); return this.onCommandSelected(selectedCommand); } - private async onCommandSelected(chosenItem?: IExtensionCommandQuickPickItem): Promise { + private async onCommandSelected( + chosenItem?: IExtensionCommandQuickPickItem, + ): Promise { if (chosenItem !== undefined) { const client = await LanguageClientConsumer.getLanguageClient(); - await client.sendRequest( - InvokeExtensionCommandRequestType, - { - name: chosenItem.command.name, - context: this.getEditorContext() - }); + await client.sendRequest(InvokeExtensionCommandRequestType, { + name: chosenItem.command.name, + context: this.getEditorContext(), + }); } } - private async insertText(details: IInsertTextRequestArguments): Promise { + private async insertText( + details: IInsertTextRequestArguments, + ): Promise { const edit = new vscode.WorkspaceEdit(); - edit.set( - vscode.Uri.parse(details.filePath), - [ - new vscode.TextEdit( - new vscode.Range( - details.insertRange.start.line, - details.insertRange.start.character, - details.insertRange.end.line, - details.insertRange.end.character), - details.insertText), - ], - ); + edit.set(vscode.Uri.parse(details.filePath), [ + new vscode.TextEdit( + new vscode.Range( + details.insertRange.start.line, + details.insertRange.start.character, + details.insertRange.end.line, + details.insertRange.end.character, + ), + details.insertText, + ), + ]); await vscode.workspace.applyEdit(edit); @@ -342,46 +416,70 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { } return { - currentFileContent: vscode.window.activeTextEditor.document.getText(), - currentFileLanguage: vscode.window.activeTextEditor.document.languageId, - currentFilePath: vscode.window.activeTextEditor.document.uri.toString(), - cursorPosition: asPosition(vscode.window.activeTextEditor.selection.active), - selectionRange: - asRange( - new vscode.Range( - vscode.window.activeTextEditor.selection.start, - vscode.window.activeTextEditor.selection.end)), + currentFileContent: + vscode.window.activeTextEditor.document.getText(), + currentFileLanguage: + vscode.window.activeTextEditor.document.languageId, + currentFilePath: + vscode.window.activeTextEditor.document.uri.toString(), + cursorPosition: asPosition( + vscode.window.activeTextEditor.selection.active, + ), + selectionRange: asRange( + new vscode.Range( + vscode.window.activeTextEditor.selection.start, + vscode.window.activeTextEditor.selection.end, + ), + ), }; } private async newFile(content: string): Promise { - const doc = await vscode.workspace.openTextDocument( - { language: "powershell", content: content }); + const doc = await vscode.workspace.openTextDocument({ + language: "powershell", + content: content, + }); await vscode.window.showTextDocument(doc); return EditorOperationResponse.Completed; } - private async openFile(openFileDetails: IOpenFileDetails): Promise { - const filePath = await this.resolveFilePathWithCwd(openFileDetails.filePath); + private async openFile( + openFileDetails: IOpenFileDetails, + ): Promise { + const filePath = await this.resolveFilePathWithCwd( + openFileDetails.filePath, + ); try { const doc = await vscode.workspace.openTextDocument(filePath); - await vscode.window.showTextDocument(doc, { preview: openFileDetails.preview }); + await vscode.window.showTextDocument(doc, { + preview: openFileDetails.preview, + }); } catch { - void this.logger.writeAndShowWarning(`File to open not found: ${filePath}`); + void this.logger.writeAndShowWarning( + `File to open not found: ${filePath}`, + ); return EditorOperationResponse.Failed; } return EditorOperationResponse.Completed; } - private async closeFile(filePath: string): Promise { + private async closeFile( + filePath: string, + ): Promise { filePath = await this.resolveFilePathWithCwd(filePath); - const doc = vscode.workspace.textDocuments.find((x) => x.uri.fsPath === filePath); + const doc = vscode.workspace.textDocuments.find( + (x) => x.uri.fsPath === filePath, + ); if (doc != undefined && !doc.isClosed) { await vscode.window.showTextDocument(doc); - await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + await vscode.commands.executeCommand( + "workbench.action.closeActiveEditor", + ); return EditorOperationResponse.Completed; } - void this.logger.writeAndShowWarning(`File to close not found or already closed: ${filePath}`); + void this.logger.writeAndShowWarning( + `File to close not found or already closed: ${filePath}`, + ); return EditorOperationResponse.Failed; } @@ -389,19 +487,30 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { * Save a file, possibly to a new path. If the save is not possible, return a completed response * @param saveFileDetails the object detailing the path of the file to save and optionally its new path to save to */ - private async saveFile(saveFileDetails: ISaveFileDetails): Promise { + private async saveFile( + saveFileDetails: ISaveFileDetails, + ): Promise { // Try to interpret the filePath as a URI, defaulting to "file://" if we don't succeed let currentFileUri: vscode.Uri; - if (saveFileDetails.filePath.startsWith("untitled") || saveFileDetails.filePath.startsWith("file")) { + if ( + saveFileDetails.filePath.startsWith("untitled") || + saveFileDetails.filePath.startsWith("file") + ) { currentFileUri = vscode.Uri.parse(saveFileDetails.filePath); } else { - const filePath = await this.resolveFilePathWithCwd(saveFileDetails.filePath); + const filePath = await this.resolveFilePathWithCwd( + saveFileDetails.filePath, + ); currentFileUri = vscode.Uri.file(filePath); } - const doc = vscode.workspace.textDocuments.find((x) => x.uri.fsPath === currentFileUri.fsPath); + const doc = vscode.workspace.textDocuments.find( + (x) => x.uri.fsPath === currentFileUri.fsPath, + ); if (doc === undefined) { - void this.logger.writeAndShowWarning(`File to save not found: ${currentFileUri.fsPath}`); + void this.logger.writeAndShowWarning( + `File to save not found: ${currentFileUri.fsPath}`, + ); return EditorOperationResponse.Failed; } @@ -418,12 +527,17 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { // Special case where we interpret a path as relative to the current // file, not the CWD! if (!path.isAbsolute(newFilePath)) { - newFilePath = path.join(path.dirname(currentFileUri.fsPath), newFilePath); + newFilePath = path.join( + path.dirname(currentFileUri.fsPath), + newFilePath, + ); } } else if (currentFileUri.scheme === "untitled") { // We need a new name to save an untitled file if (newFilePath === undefined) { - void this.logger.writeAndShowWarning("Cannot save untitled file! Try SaveAs(\"path/to/file.ps1\") instead."); + void this.logger.writeAndShowWarning( + 'Cannot save untitled file! Try SaveAs("path/to/file.ps1") instead.', + ); return EditorOperationResponse.Failed; } @@ -433,7 +547,8 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { const msg = JSON.stringify(saveFileDetails, undefined, 2); void this.logger.writeAndShowWarning( `<${ExtensionCommandsFeature.name}>: Saving a document with scheme '${currentFileUri.scheme}' ` + - `is currently unsupported. Message: '${msg}'`); + `is currently unsupported. Message: '${msg}'`, + ); return EditorOperationResponse.Failed; } @@ -445,16 +560,22 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { * @param documentUri the URI of the vscode document to save * @param filePath the absolute path to save the document contents to */ - private async saveFileAs(doc: vscode.TextDocument, filePath: string): Promise { + private async saveFileAs( + doc: vscode.TextDocument, + filePath: string, + ): Promise { // Write the old document's contents to the new document path const newFileUri = vscode.Uri.file(filePath); try { await vscode.workspace.fs.writeFile( newFileUri, - Buffer.from(doc.getText())); + Buffer.from(doc.getText()), + ); } catch (err) { - void this.logger.writeAndShowWarning(`<${ExtensionCommandsFeature.name}>: ` + - `Unable to save file to path '${filePath}': ${err}`); + void this.logger.writeAndShowWarning( + `<${ExtensionCommandsFeature.name}>: ` + + `Unable to save file to path '${filePath}': ${err}`, + ); return EditorOperationResponse.Failed; } @@ -473,12 +594,15 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { return filePath; } - private setSelection(details: ISetSelectionRequestArguments): EditorOperationResponse { + private setSelection( + details: ISetSelectionRequestArguments, + ): EditorOperationResponse { if (vscode.window.activeTextEditor !== undefined) { vscode.window.activeTextEditor.selections = [ new vscode.Selection( asCodePosition(details.selectionRange.start), - asCodePosition(details.selectionRange.end)), + asCodePosition(details.selectionRange.end), + ), ]; return EditorOperationResponse.Completed; } @@ -500,13 +624,20 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { return EditorOperationResponse.Completed; } - private setStatusBarMessage(messageDetails: IStatusBarMessageDetails): EditorOperationResponse { + private setStatusBarMessage( + messageDetails: IStatusBarMessageDetails, + ): EditorOperationResponse { if (messageDetails.timeout) { this.statusBarMessages.push( - vscode.window.setStatusBarMessage(messageDetails.message, messageDetails.timeout)); + vscode.window.setStatusBarMessage( + messageDetails.message, + messageDetails.timeout, + ), + ); } else { this.statusBarMessages.push( - vscode.window.setStatusBarMessage(messageDetails.message)); + vscode.window.setStatusBarMessage(messageDetails.message), + ); } return EditorOperationResponse.Completed; } diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts index 974436da4e..cafd856c26 100644 --- a/src/features/ExternalApi.ts +++ b/src/features/ExternalApi.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import * as vscode from "vscode"; import { v4 as uuidv4 } from "uuid"; +import * as vscode from "vscode"; import type { ILogger } from "../logging"; import { SessionManager } from "../session"; @@ -16,7 +16,9 @@ export interface IExternalPowerShellDetails { export interface IPowerShellExtensionClient { registerExternalExtension(id: string, apiVersion?: string): string; unregisterExternalExtension(uuid: string): boolean; - getPowerShellVersionDetails(uuid: string): Promise; + getPowerShellVersionDetails( + uuid: string, + ): Promise; waitUntilStarted(uuid: string): Promise; getStorageUri(): vscode.Uri; getLogUri(): vscode.Uri; @@ -34,13 +36,16 @@ NOTE: At some point, we should release a helper npm package that wraps the API a */ export class ExternalApiFeature implements IPowerShellExtensionClient { - private static readonly registeredExternalExtension: Map = new Map(); + private static readonly registeredExternalExtension: Map< + string, + IExternalExtension + > = new Map(); constructor( private extensionContext: vscode.ExtensionContext, private sessionManager: SessionManager, - private logger: ILogger) { - } + private logger: ILogger, + ) {} /* DESCRIPTION: @@ -56,10 +61,14 @@ export class ExternalApiFeature implements IPowerShellExtensionClient { string session uuid */ public registerExternalExtension(id: string, apiVersion = "v1"): string { - this.logger.writeDebug(`Registering extension '${id}' for use with API version '${apiVersion}'.`); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const [_name, externalExtension] of ExternalApiFeature.registeredExternalExtension) { + this.logger.writeDebug( + `Registering extension '${id}' for use with API version '${apiVersion}'.`, + ); + + for (const [ + _name, + externalExtension, + ] of ExternalApiFeature.registeredExternalExtension) { if (externalExtension.id === id) { const message = `The extension '${id}' is already registered.`; this.logger.writeWarning(message); @@ -67,19 +76,26 @@ export class ExternalApiFeature implements IPowerShellExtensionClient { } } - if (!vscode.extensions.all.some(ext => ext.id === id)) { - throw new Error(`No extension installed with id '${id}'. You must use a valid extension id.`); + if (!vscode.extensions.all.some((ext) => ext.id === id)) { + throw new Error( + `No extension installed with id '${id}'. You must use a valid extension id.`, + ); } // Our ID is only only allowed to be used in our unit tests. - if (id === "ms-vscode.powershell" && !(this.extensionContext.extensionMode === vscode.ExtensionMode.Test)) { - throw new Error("You can't use the PowerShell extension's id in this registration."); + if ( + id === "ms-vscode.powershell" && + !(this.extensionContext.extensionMode === vscode.ExtensionMode.Test) + ) { + throw new Error( + "You can't use the PowerShell extension's id in this registration.", + ); } const uuid = uuidv4(); ExternalApiFeature.registeredExternalExtension.set(uuid, { id, - apiVersion + apiVersion, }); return uuid; } @@ -97,9 +113,13 @@ export class ExternalApiFeature implements IPowerShellExtensionClient { true if it worked, otherwise throws an error. */ public unregisterExternalExtension(uuid = ""): boolean { - this.logger.writeDebug(`Unregistering extension with session UUID: ${uuid}`); + this.logger.writeDebug( + `Unregistering extension with session UUID: ${uuid}`, + ); if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) { - throw new Error(`No extension registered with session UUID: ${uuid}`); + throw new Error( + `No extension registered with session UUID: ${uuid}`, + ); } return true; } @@ -107,7 +127,8 @@ export class ExternalApiFeature implements IPowerShellExtensionClient { private getRegisteredExtension(uuid = ""): IExternalExtension { if (!ExternalApiFeature.registeredExternalExtension.has(uuid)) { throw new Error( - "UUID provided was invalid, make sure you ran the 'powershellExtensionClient.registerExternalExtension(extensionId)' method and pass in the UUID that it returns to subsequent methods."); + "UUID provided was invalid, make sure you ran the 'powershellExtensionClient.registerExternalExtension(extensionId)' method and pass in the UUID that it returns to subsequent methods.", + ); } // TODO: When we have more than one API version, make sure to include a check here. @@ -132,18 +153,26 @@ export class ExternalApiFeature implements IPowerShellExtensionClient { architecture: string; } */ - public async getPowerShellVersionDetails(uuid = ""): Promise { + public async getPowerShellVersionDetails( + uuid = "", + ): Promise { const extension = this.getRegisteredExtension(uuid); - this.logger.writeDebug(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`); + this.logger.writeDebug( + `Extension '${extension.id}' called 'getPowerShellVersionDetails'.`, + ); await this.sessionManager.waitUntilStarted(); - const versionDetails = this.sessionManager.getPowerShellVersionDetails(); + const versionDetails = + this.sessionManager.getPowerShellVersionDetails(); return { - exePath: this.sessionManager.PowerShellExeDetails?.exePath ?? "unknown", + exePath: + this.sessionManager.PowerShellExeDetails?.exePath ?? "unknown", version: versionDetails?.version ?? "unknown", - displayName: this.sessionManager.PowerShellExeDetails?.displayName ?? "unknown", // comes from the Session Menu - architecture: versionDetails?.architecture ?? "unknown" + displayName: + this.sessionManager.PowerShellExeDetails?.displayName ?? + "unknown", // comes from the Session Menu + architecture: versionDetails?.architecture ?? "unknown", }; } /* @@ -162,18 +191,20 @@ export class ExternalApiFeature implements IPowerShellExtensionClient { */ public async waitUntilStarted(uuid = ""): Promise { const extension = this.getRegisteredExtension(uuid); - this.logger.writeDebug(`Extension '${extension.id}' called 'waitUntilStarted'.`); + this.logger.writeDebug( + `Extension '${extension.id}' called 'waitUntilStarted'.`, + ); await this.sessionManager.waitUntilStarted(); } public getStorageUri(): vscode.Uri { // We have to override the scheme because it defaults to // 'vscode-userdata' which breaks UNC paths. - return this.extensionContext.globalStorageUri.with({ scheme: "file"}); + return this.extensionContext.globalStorageUri.with({ scheme: "file" }); } public getLogUri(): vscode.Uri { - return this.extensionContext.logUri.with({ scheme: "file"}); + return this.extensionContext.logUri.with({ scheme: "file" }); } public dispose(): void { diff --git a/src/features/GenerateBugReport.ts b/src/features/GenerateBugReport.ts index 4ec7cec571..6539f978d9 100644 --- a/src/features/GenerateBugReport.ts +++ b/src/features/GenerateBugReport.ts @@ -8,21 +8,30 @@ import { SessionManager } from "../session"; const issuesUrl = "https://github.com/PowerShell/vscode-powershell/issues/new?"; export class GenerateBugReportFeature implements vscode.Disposable { - private command: vscode.Disposable; constructor(private sessionManager: SessionManager) { - this.command = vscode.commands.registerCommand("PowerShell.GenerateBugReport", async () => { - const params = [ - "labels=Issue-Bug", - "template=bug-report.yml", - "powershell-version=" + this.getRuntimeInfo(), - "vscode-version=" + vscode.version + "\n" + process.arch, - "extension-version=" + sessionManager.Publisher + "." + sessionManager.HostName + "@" + sessionManager.HostVersion, - ]; - const url = vscode.Uri.parse(issuesUrl + encodeURIComponent(params.join("&"))); - await vscode.env.openExternal(url); - }); + this.command = vscode.commands.registerCommand( + "PowerShell.GenerateBugReport", + async () => { + const params = [ + "labels=Issue-Bug", + "template=bug-report.yml", + "powershell-version=" + this.getRuntimeInfo(), + "vscode-version=" + vscode.version + "\n" + process.arch, + "extension-version=" + + sessionManager.Publisher + + "." + + sessionManager.HostName + + "@" + + sessionManager.HostVersion, + ]; + const url = vscode.Uri.parse( + issuesUrl + encodeURIComponent(params.join("&")), + ); + await vscode.env.openExternal(url); + }, + ); } public dispose(): void { @@ -35,7 +44,13 @@ export class GenerateBugReportFeature implements vscode.Disposable { } const child = child_process.spawnSync( this.sessionManager.PowerShellExeDetails.exePath, - ["-NoProfile", "-NoLogo", "-Command", "$PSVersionTable | Out-String"]); + [ + "-NoProfile", + "-NoLogo", + "-Command", + "$PSVersionTable | Out-String", + ], + ); // Replace semicolons as they'll cause the URI component to truncate return child.stdout.toString().trim().replace(";", ","); } diff --git a/src/features/GetCommands.ts b/src/features/GetCommands.ts index f85aad28f8..52d007532e 100644 --- a/src/features/GetCommands.ts +++ b/src/features/GetCommands.ts @@ -19,7 +19,9 @@ interface ICommand { * RequestType sent over to PSES. * Expects: ICommand to be returned */ -export const GetCommandRequestType = new RequestType0("powerShell/getCommand"); +export const GetCommandRequestType = new RequestType0( + "powerShell/getCommand", +); /** * A PowerShell Command listing feature. Implements a treeview control. @@ -32,14 +34,25 @@ export class GetCommandsFeature extends LanguageClientConsumer { constructor() { super(); this.commands = [ - vscode.commands.registerCommand("PowerShell.RefreshCommandsExplorer", - async () => { await this.CommandExplorerRefresh(); }), - vscode.commands.registerCommand("PowerShell.InsertCommand", async (item) => { await this.InsertCommand(item); }) + vscode.commands.registerCommand( + "PowerShell.RefreshCommandsExplorer", + async () => { + await this.CommandExplorerRefresh(); + }, + ), + vscode.commands.registerCommand( + "PowerShell.InsertCommand", + async (item) => { + await this.InsertCommand(item); + }, + ), ]; this.commandsExplorerProvider = new CommandsExplorerProvider(); - this.commandsExplorerTreeView = vscode.window.createTreeView("PowerShellCommands", - { treeDataProvider: this.commandsExplorerProvider }); + this.commandsExplorerTreeView = vscode.window.createTreeView( + "PowerShellCommands", + { treeDataProvider: this.commandsExplorerProvider }, + ); // Refresh the command explorer when the view is visible this.commandsExplorerTreeView.onDidChangeVisibility(async (e) => { @@ -57,7 +70,9 @@ export class GetCommandsFeature extends LanguageClientConsumer { public override onLanguageClientSet(_languageClient: LanguageClient): void { if (this.commandsExplorerTreeView.visible) { - void vscode.commands.executeCommand("PowerShell.RefreshCommandsExplorer"); + void vscode.commands.executeCommand( + "PowerShell.RefreshCommandsExplorer", + ); } } @@ -65,13 +80,19 @@ export class GetCommandsFeature extends LanguageClientConsumer { const client = await LanguageClientConsumer.getLanguageClient(); const result = await client.sendRequest(GetCommandRequestType); const exclusions = getSettings().sideBar.CommandExplorerExcludeFilter; - const excludeFilter = exclusions.map((filter: string) => filter.toLowerCase()); - const filteredResult = result.filter((command) => (!excludeFilter.includes(command.moduleName.toLowerCase()))); - this.commandsExplorerProvider.powerShellCommands = filteredResult.map(toCommand); + const excludeFilter = exclusions.map((filter: string) => + filter.toLowerCase(), + ); + const filteredResult = result.filter( + (command) => + !excludeFilter.includes(command.moduleName.toLowerCase()), + ); + this.commandsExplorerProvider.powerShellCommands = + filteredResult.map(toCommand); this.commandsExplorerProvider.refresh(); } - private async InsertCommand(item: { Name: string; }): Promise { + private async InsertCommand(item: { Name: string }): Promise { const editor = vscode.window.activeTextEditor; if (editor === undefined) { return; @@ -79,7 +100,12 @@ export class GetCommandsFeature extends LanguageClientConsumer { const sls = editor.selection.start; const sle = editor.selection.end; - const range = new vscode.Range(sls.line, sls.character, sle.line, sle.character); + const range = new vscode.Range( + sls.line, + sls.character, + sle.line, + sle.character, + ); await editor.edit((editBuilder) => { editBuilder.replace(range, item.Name); }); @@ -89,7 +115,8 @@ export class GetCommandsFeature extends LanguageClientConsumer { class CommandsExplorerProvider implements vscode.TreeDataProvider { public readonly onDidChangeTreeData: vscode.Event; public powerShellCommands: Command[] = []; - private didChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + private didChangeTreeData: vscode.EventEmitter = + new vscode.EventEmitter(); constructor() { this.onDidChangeTreeData = this.didChangeTreeData.event; @@ -125,7 +152,8 @@ class Command extends vscode.TreeItem { public readonly defaultParameterSet: string, public readonly ParameterSets: object, public readonly Parameters: object, - public override readonly collapsibleState = vscode.TreeItemCollapsibleState.None, + public override readonly collapsibleState = vscode + .TreeItemCollapsibleState.None, ) { super(Name, collapsibleState); } diff --git a/src/features/HelpCompletion.ts b/src/features/HelpCompletion.ts index 9711b93f35..bf439748d2 100644 --- a/src/features/HelpCompletion.ts +++ b/src/features/HelpCompletion.ts @@ -2,25 +2,37 @@ // Licensed under the MIT License. import { - Disposable, EndOfLine, Range, SnippetString, - type TextDocument, type TextDocumentChangeEvent, window, workspace + Disposable, + EndOfLine, + Range, + SnippetString, + type TextDocument, + type TextDocumentChangeEvent, + window, + workspace, } from "vscode"; import { RequestType } from "vscode-languageclient"; import { LanguageClient } from "vscode-languageclient/node"; -import { Settings, CommentType, getSettings } from "../settings"; import { LanguageClientConsumer } from "../languageClientConsumer"; +import { CommentType, getSettings, Settings } from "../settings"; -interface ICommentHelpRequestArguments { -} +interface ICommentHelpRequestArguments {} interface ICommentHelpRequestResponse { - content: string[] + content: string[]; } -export const CommentHelpRequestType = - new RequestType("powerShell/getCommentHelp"); +export const CommentHelpRequestType = new RequestType< + ICommentHelpRequestArguments, + ICommentHelpRequestResponse, + void +>("powerShell/getCommentHelp"); -enum SearchState { Searching, Locked, Found } +enum SearchState { + Searching, + Locked, + Found, +} export class HelpCompletionFeature extends LanguageClientConsumer { private helpCompletionProvider: HelpCompletionProvider | undefined; @@ -33,7 +45,9 @@ export class HelpCompletionFeature extends LanguageClientConsumer { if (this.settings.helpCompletion !== CommentType.Disabled) { this.helpCompletionProvider = new HelpCompletionProvider(); - this.disposable = workspace.onDidChangeTextDocument(async (e) => { await this.onEvent(e); }); + this.disposable = workspace.onDidChangeTextDocument(async (e) => { + await this.onEvent(e); + }); } } @@ -58,7 +72,8 @@ export class HelpCompletionFeature extends LanguageClientConsumer { this.helpCompletionProvider?.updateState( changeEvent.document, changeEvent.contentChanges[0].text, - changeEvent.contentChanges[0].range); + changeEvent.contentChanges[0].range, + ); // TODO: Raise an event when trigger is found, and attach complete() to the event. if (this.helpCompletionProvider?.triggerFound) { @@ -85,30 +100,37 @@ class TriggerFinder { public updateState(document: TextDocument, changeText: string): void { switch (this.state) { - case SearchState.Searching: - // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with - if (changeText.length === 1 && changeText[0] === this.triggerCharacters[this.count]) { - this.state = SearchState.Locked; - this.document = document; - this.count++; - } - break; - - case SearchState.Locked: - // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with - if (document === this.document && changeText.length === 1 && changeText[0] === this.triggerCharacters[this.count]) { - this.count++; - if (this.count === this.triggerCharacters.length) { - this.state = SearchState.Found; + case SearchState.Searching: + if ( + changeText.length === 1 && + // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with + changeText[0] === this.triggerCharacters[this.count] + ) { + this.state = SearchState.Locked; + this.document = document; + this.count++; } - } else { - this.reset(); - } - break; + break; + + case SearchState.Locked: + if ( + document === this.document && + changeText.length === 1 && + // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with + changeText[0] === this.triggerCharacters[this.count] + ) { + this.count++; + if (this.count === this.triggerCharacters.length) { + this.state = SearchState.Found; + } + } else { + this.reset(); + } + break; - default: - this.reset(); - break; + default: + this.reset(); + break; } } @@ -134,10 +156,16 @@ class HelpCompletionProvider extends LanguageClientConsumer { return this.triggerFinderHelpComment.found; } - // eslint-disable-next-line @typescript-eslint/no-empty-function - public override onLanguageClientSet(_languageClient: LanguageClient): void {} + public override onLanguageClientSet( + _languageClient: LanguageClient, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): void {} - public updateState(document: TextDocument, changeText: string, changeRange: Range): void { + public updateState( + document: TextDocument, + changeText: string, + changeRange: Range, + ): void { this.lastDocument = document; this.lastChangeRange = changeRange; this.triggerFinderHelpComment.updateState(document, changeText); @@ -148,7 +176,10 @@ class HelpCompletionProvider extends LanguageClientConsumer { } public async complete(): Promise { - if (this.lastChangeRange === undefined || this.lastDocument === undefined) { + if ( + this.lastChangeRange === undefined || + this.lastDocument === undefined + ) { return; } @@ -159,26 +190,31 @@ class HelpCompletionProvider extends LanguageClientConsumer { const result = await client.sendRequest(CommentHelpRequestType, { documentUri: doc.uri.toString(), triggerPosition: triggerStartPos, - blockComment: this.settings.helpCompletion === CommentType.BlockComment, + blockComment: + this.settings.helpCompletion === CommentType.BlockComment, }); if (result.content.length === 0) { return; } - const replaceRange = new Range(triggerStartPos.translate(0, -1), triggerStartPos.translate(0, 1)); + const replaceRange = new Range( + triggerStartPos.translate(0, -1), + triggerStartPos.translate(0, 1), + ); // TODO: add indentation level to the help content // Trim leading whitespace (used by the rule for indentation) as VSCode takes care of the indentation. // Trim the last empty line and join the strings. const lines: string[] = result.content; - const text = lines - .map((x) => x.trimStart()) - .join(this.getEOL(doc.eol)); + const text = lines.map((x) => x.trimStart()).join(this.getEOL(doc.eol)); const snippetString = new SnippetString(text); - await window.activeTextEditor?.insertSnippet(snippetString, replaceRange); + await window.activeTextEditor?.insertSnippet( + snippetString, + replaceRange, + ); } private getEOL(eol: EndOfLine): string { diff --git a/src/features/ISECompatibility.ts b/src/features/ISECompatibility.ts index 8cb96711fa..747bb4f6f4 100644 --- a/src/features/ISECompatibility.ts +++ b/src/features/ISECompatibility.ts @@ -17,13 +17,29 @@ export class ISECompatibilityFeature implements vscode.Disposable { public static settings: ISetting[] = [ { path: "debug", name: "openDebug", value: "neverOpen" }, { path: "editor", name: "tabCompletion", value: "on" }, - { path: "powershell.integratedConsole", name: "focusConsoleOnExecute", value: false }, + { + path: "powershell.integratedConsole", + name: "focusConsoleOnExecute", + value: false, + }, { path: "files", name: "defaultLanguage", value: "powershell" }, { path: "workbench", name: "colorTheme", value: "PowerShell ISE" }, - { path: "editor", name: "wordSeparators", value: "`~!@#%^&*()-=+[{]}\\|;:'\",.<>/?" }, - { path: "powershell.buttons", name: "showPanelMovementButtons", value: true }, + { + path: "editor", + name: "wordSeparators", + value: "`~!@#%^&*()-=+[{]}\\|;:'\",.<>/?", + }, + { + path: "powershell.buttons", + name: "showPanelMovementButtons", + value: true, + }, { path: "powershell.codeFolding", name: "showLastLine", value: false }, - { path: "powershell.sideBar", name: "CommandExplorerVisibility", value: true } + { + path: "powershell.sideBar", + name: "CommandExplorerVisibility", + value: true, + }, ]; private commands: vscode.Disposable[] = []; @@ -31,12 +47,33 @@ export class ISECompatibilityFeature implements vscode.Disposable { private originalSettings: Record = {}; constructor() { - const testSetting = ISECompatibilityFeature.settings[ISECompatibilityFeature.settings.length - 1]; - this.iseModeEnabled = vscode.workspace.getConfiguration(testSetting.path).get(testSetting.name) === testSetting.value; + const testSetting = + ISECompatibilityFeature.settings[ + ISECompatibilityFeature.settings.length - 1 + ]; + this.iseModeEnabled = + vscode.workspace + .getConfiguration(testSetting.path) + .get(testSetting.name) === testSetting.value; this.commands = [ - vscode.commands.registerCommand("PowerShell.EnableISEMode", async () => { await this.EnableISEMode(); }), - vscode.commands.registerCommand("PowerShell.DisableISEMode", async () => { await this.DisableISEMode(); }), - vscode.commands.registerCommand("PowerShell.ToggleISEMode", async () => { await this.ToggleISEMode(); }) + vscode.commands.registerCommand( + "PowerShell.EnableISEMode", + async () => { + await this.EnableISEMode(); + }, + ), + vscode.commands.registerCommand( + "PowerShell.DisableISEMode", + async () => { + await this.DisableISEMode(); + }, + ), + vscode.commands.registerCommand( + "PowerShell.ToggleISEMode", + async () => { + await this.ToggleISEMode(); + }, + ), ]; } @@ -50,8 +87,11 @@ export class ISECompatibilityFeature implements vscode.Disposable { this.iseModeEnabled = true; for (const iseSetting of ISECompatibilityFeature.settings) { try { - const config = vscode.workspace.getConfiguration(iseSetting.path); - this.originalSettings[iseSetting.path + iseSetting.name] = config.get(iseSetting.name); + const config = vscode.workspace.getConfiguration( + iseSetting.path, + ); + this.originalSettings[iseSetting.path + iseSetting.name] = + config.get(iseSetting.name); await config.update(iseSetting.name, iseSetting.value, true); } catch { // The `update` call can fail if the setting doesn't exist. This @@ -63,7 +103,9 @@ export class ISECompatibilityFeature implements vscode.Disposable { } // Show the PowerShell view container which has the Command Explorer view - await vscode.commands.executeCommand("workbench.view.extension.PowerShell"); + await vscode.commands.executeCommand( + "workbench.view.extension.PowerShell", + ); } private async DisableISEMode(): Promise { @@ -72,7 +114,11 @@ export class ISECompatibilityFeature implements vscode.Disposable { const config = vscode.workspace.getConfiguration(iseSetting.path); const currently = config.get(iseSetting.name); if (currently === iseSetting.value) { - await config.update(iseSetting.name, this.originalSettings[iseSetting.path + iseSetting.name], true); + await config.update( + iseSetting.name, + this.originalSettings[iseSetting.path + iseSetting.name], + true, + ); } } } diff --git a/src/features/OpenInISE.ts b/src/features/OpenInISE.ts index ffc6993c1b..808ee59db7 100644 --- a/src/features/OpenInISE.ts +++ b/src/features/OpenInISE.ts @@ -8,26 +8,29 @@ export class OpenInISEFeature implements vscode.Disposable { private command: vscode.Disposable; constructor() { - this.command = vscode.commands.registerCommand("PowerShell.OpenInISE", () => { - const editor = vscode.window.activeTextEditor; - if (editor === undefined) { - return; - } - - const document = editor.document; - const uri = document.uri; - let ISEPath = process.env.windir ?? "C:\\Windows"; - - if (process.env.PROCESSOR_ARCHITEW6432 !== undefined) { - ISEPath += "\\Sysnative"; - } else { - ISEPath += "\\System32"; - } - - ISEPath += "\\WindowsPowerShell\\v1.0\\powershell_ise.exe"; - - ChildProcess.exec(`${ISEPath} -File "${uri.fsPath}"`).unref(); - }); + this.command = vscode.commands.registerCommand( + "PowerShell.OpenInISE", + () => { + const editor = vscode.window.activeTextEditor; + if (editor === undefined) { + return; + } + + const document = editor.document; + const uri = document.uri; + let ISEPath = process.env.windir ?? "C:\\Windows"; + + if (process.env.PROCESSOR_ARCHITEW6432 !== undefined) { + ISEPath += "\\Sysnative"; + } else { + ISEPath += "\\System32"; + } + + ISEPath += "\\WindowsPowerShell\\v1.0\\powershell_ise.exe"; + + ChildProcess.exec(`${ISEPath} -File "${uri.fsPath}"`).unref(); + }, + ); } public dispose(): void { diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index a2f4c10148..486bcd8aab 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -2,10 +2,10 @@ // Licensed under the MIT License. import * as path from "path"; -import vscode = require("vscode"); import type { ILogger } from "../logging"; import { SessionManager } from "../session"; -import { getSettings, getChosenWorkspace } from "../settings"; +import { getChosenWorkspace, getSettings } from "../settings"; +import vscode = require("vscode"); import utils = require("../utils"); enum LaunchType { @@ -17,29 +17,56 @@ export class PesterTestsFeature implements vscode.Disposable { private commands: vscode.Disposable[]; private invokePesterStubScriptPath: string; - constructor(private sessionManager: SessionManager, private logger: ILogger) { - this.invokePesterStubScriptPath = path.resolve(__dirname, "../modules/PowerShellEditorServices/InvokePesterStub.ps1"); + constructor( + private sessionManager: SessionManager, + private logger: ILogger, + ) { + this.invokePesterStubScriptPath = path.resolve( + __dirname, + "../modules/PowerShellEditorServices/InvokePesterStub.ps1", + ); this.commands = [ // File context-menu command - Run Pester Tests vscode.commands.registerCommand( "PowerShell.RunPesterTestsFromFile", (fileUri?) => { - return this.launchAllTestsInActiveEditor(LaunchType.Run, fileUri); - }), + return this.launchAllTestsInActiveEditor( + LaunchType.Run, + fileUri, + ); + }, + ), // File context-menu command - Debug Pester Tests vscode.commands.registerCommand( "PowerShell.DebugPesterTestsFromFile", (fileUri?) => { - return this.launchAllTestsInActiveEditor(LaunchType.Debug, fileUri); - }), + return this.launchAllTestsInActiveEditor( + LaunchType.Debug, + fileUri, + ); + }, + ), // This command is provided for usage by PowerShellEditorServices (PSES) only vscode.commands.registerCommand( "PowerShell.RunPesterTests", - (uriString, runInDebugger, describeBlockName?, describeBlockLineNumber?, outputPath?) => { - return this.launchTests(vscode.Uri.parse(uriString), runInDebugger, describeBlockName, describeBlockLineNumber, outputPath); - }) + ( + uriString, + runInDebugger, + describeBlockName?, + describeBlockLineNumber?, + outputPath?, + ) => { + return this.launchTests( + vscode.Uri.parse(uriString), + runInDebugger, + describeBlockName, + describeBlockLineNumber, + outputPath, + ); + }, + ), ]; } @@ -51,8 +78,8 @@ export class PesterTestsFeature implements vscode.Disposable { private async launchAllTestsInActiveEditor( launchType: LaunchType, - fileUri?: vscode.Uri): Promise { - + fileUri?: vscode.Uri, + ): Promise { fileUri ??= vscode.window.activeTextEditor?.document.uri; if (fileUri === undefined) { @@ -68,10 +95,16 @@ export class PesterTestsFeature implements vscode.Disposable { runInDebugger: boolean, describeBlockName?: string, describeBlockLineNumber?: number, - outputPath?: string): Promise { - + outputPath?: string, + ): Promise { const launchType = runInDebugger ? LaunchType.Debug : LaunchType.Run; - const launchConfig = this.createLaunchConfig(fileUri, launchType, describeBlockName, describeBlockLineNumber, outputPath); + const launchConfig = this.createLaunchConfig( + fileUri, + launchType, + describeBlockName, + describeBlockLineNumber, + outputPath, + ); return this.launch(launchConfig); } @@ -80,8 +113,8 @@ export class PesterTestsFeature implements vscode.Disposable { launchType: LaunchType, testName?: string, lineNum?: number, - outputPath?: string): vscode.DebugConfiguration { - + outputPath?: string, + ): vscode.DebugConfiguration { const settings = getSettings(); const launchConfig = { request: "launch", @@ -93,14 +126,18 @@ export class PesterTestsFeature implements vscode.Disposable { `'${utils.escapeSingleQuotes(fileUri.fsPath)}'`, ], internalConsoleOptions: "neverOpen", - noDebug: (launchType === LaunchType.Run), - createTemporaryIntegratedConsole: settings.debugging.createTemporaryIntegratedConsole + noDebug: launchType === LaunchType.Run, + createTemporaryIntegratedConsole: + settings.debugging.createTemporaryIntegratedConsole, }; if (lineNum) { launchConfig.args.push("-LineNumber", `${lineNum}`); } else if (testName) { - launchConfig.args.push("-TestName", `'${utils.escapeSingleQuotes(testName)}'`); + launchConfig.args.push( + "-TestName", + `'${utils.escapeSingleQuotes(testName)}'`, + ); } else { launchConfig.args.push("-All"); } @@ -110,10 +147,15 @@ export class PesterTestsFeature implements vscode.Disposable { } if (launchType === LaunchType.Debug) { - launchConfig.args.push("-Output", `'${settings.pester.debugOutputVerbosity}'`); - } - else { - launchConfig.args.push("-Output", `'${settings.pester.outputVerbosity}'`); + launchConfig.args.push( + "-Output", + `'${settings.pester.debugOutputVerbosity}'`, + ); + } else { + launchConfig.args.push( + "-Output", + `'${settings.pester.outputVerbosity}'`, + ); } if (outputPath) { @@ -123,14 +165,21 @@ export class PesterTestsFeature implements vscode.Disposable { return launchConfig; } - private async launch(launchConfig: vscode.DebugConfiguration): Promise { + private async launch( + launchConfig: vscode.DebugConfiguration, + ): Promise { // Create or show the interactive console // TODO: #367 Check if "newSession" mode is configured this.sessionManager.showDebugTerminal(true); // Ensure the necessary script exists (for testing). The debugger will // start regardless, but we also pass its success along. - return await utils.checkIfFileExists(this.invokePesterStubScriptPath) - && vscode.debug.startDebugging(await getChosenWorkspace(this.logger), launchConfig); + return ( + (await utils.checkIfFileExists(this.invokePesterStubScriptPath)) && + vscode.debug.startDebugging( + await getChosenWorkspace(this.logger), + launchConfig, + ) + ); } } diff --git a/src/features/RemoteFiles.ts b/src/features/RemoteFiles.ts index 765b6d99a6..46164b5de4 100644 --- a/src/features/RemoteFiles.ts +++ b/src/features/RemoteFiles.ts @@ -4,9 +4,12 @@ import os = require("os"); import path = require("path"); import vscode = require("vscode"); -import { NotificationType, TextDocumentIdentifier } from "vscode-languageclient"; -import { LanguageClientConsumer } from "../languageClientConsumer"; +import { + NotificationType, + TextDocumentIdentifier, +} from "vscode-languageclient"; import type { LanguageClient } from "vscode-languageclient/node"; +import { LanguageClientConsumer } from "../languageClientConsumer"; // NOTE: The following two DidSaveTextDocument* types will // be removed when #593 gets fixed. @@ -19,8 +22,7 @@ export interface IDidSaveTextDocumentParams { } export const DidSaveTextDocumentNotificationType = - new NotificationType( - "textDocument/didSave"); + new NotificationType("textDocument/didSave"); export class RemoteFilesFeature extends LanguageClientConsumer { private command: vscode.Disposable; @@ -30,9 +32,9 @@ export class RemoteFilesFeature extends LanguageClientConsumer { super(); // Get the common PowerShell Editor Services temporary file path // so that remote files from previous sessions can be closed. - this.tempSessionPathPrefix = - path.join(os.tmpdir(), "PSES-") - .toLowerCase(); + this.tempSessionPathPrefix = path + .join(os.tmpdir(), "PSES-") + .toLowerCase(); // At startup, close any lingering temporary remote files this.closeRemoteFiles(); @@ -43,14 +45,19 @@ export class RemoteFilesFeature extends LanguageClientConsumer { await client.sendNotification( DidSaveTextDocumentNotificationType, { - textDocument: TextDocumentIdentifier.create(doc.uri.toString()), - }); + textDocument: TextDocumentIdentifier.create( + doc.uri.toString(), + ), + }, + ); } }); } - // eslint-disable-next-line @typescript-eslint/no-empty-function - public override onLanguageClientSet(_languageClient: LanguageClient): void {} + public override onLanguageClientSet( + _languageClient: LanguageClient, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): void {} public dispose(): void { this.command.dispose(); @@ -59,12 +66,15 @@ export class RemoteFilesFeature extends LanguageClientConsumer { } private isDocumentRemote(doc: vscode.TextDocument): boolean { - return doc.fileName.toLowerCase().startsWith(this.tempSessionPathPrefix); + return doc.fileName + .toLowerCase() + .startsWith(this.tempSessionPathPrefix); } private closeRemoteFiles(): void { - const remoteDocuments = - vscode.workspace.textDocuments.filter((doc) => this.isDocumentRemote(doc)); + const remoteDocuments = vscode.workspace.textDocuments.filter((doc) => + this.isDocumentRemote(doc), + ); async function innerCloseFiles(): Promise { const doc = remoteDocuments.pop(); @@ -73,7 +83,9 @@ export class RemoteFilesFeature extends LanguageClientConsumer { } await vscode.window.showTextDocument(doc); - await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + await vscode.commands.executeCommand( + "workbench.action.closeActiveEditor", + ); await innerCloseFiles(); } diff --git a/src/features/ShowHelp.ts b/src/features/ShowHelp.ts index 59616e5274..6af843e53b 100644 --- a/src/features/ShowHelp.ts +++ b/src/features/ShowHelp.ts @@ -3,11 +3,10 @@ import vscode = require("vscode"); import { NotificationType } from "vscode-languageclient"; -import { LanguageClientConsumer } from "../languageClientConsumer"; import type { LanguageClient } from "vscode-languageclient/node"; +import { LanguageClientConsumer } from "../languageClientConsumer"; -interface IShowHelpNotificationArguments { -} +interface IShowHelpNotificationArguments {} export const ShowHelpNotificationType = new NotificationType("powerShell/showHelp"); @@ -17,33 +16,42 @@ export class ShowHelpFeature extends LanguageClientConsumer { constructor() { super(); - this.command = vscode.commands.registerCommand("PowerShell.ShowHelp", async (item?) => { - if (!item?.Name) { - - const editor = vscode.window.activeTextEditor; - if (editor === undefined) { - return; + this.command = vscode.commands.registerCommand( + "PowerShell.ShowHelp", + async (item?) => { + if (!item?.Name) { + const editor = vscode.window.activeTextEditor; + if (editor === undefined) { + return; + } + + const selection = editor.selection; + const doc = editor.document; + const cwr = doc.getWordRangeAtPosition(selection.active); + const text = doc.getText(cwr); + + const client = + await LanguageClientConsumer.getLanguageClient(); + await client.sendNotification(ShowHelpNotificationType, { + text, + }); + } else { + const client = + await LanguageClientConsumer.getLanguageClient(); + await client.sendNotification(ShowHelpNotificationType, { + text: item.Name, + }); } - - const selection = editor.selection; - const doc = editor.document; - const cwr = doc.getWordRangeAtPosition(selection.active); - const text = doc.getText(cwr); - - const client = await LanguageClientConsumer.getLanguageClient(); - await client.sendNotification(ShowHelpNotificationType, { text }); - } else { - const client = await LanguageClientConsumer.getLanguageClient(); - await client.sendNotification(ShowHelpNotificationType, { text: item.Name }); - } - }); + }, + ); } - // eslint-disable-next-line @typescript-eslint/no-empty-function - public override onLanguageClientSet(_languageClient: LanguageClient): void {} + public override onLanguageClientSet( + _languageClient: LanguageClient, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): void {} public dispose(): void { this.command.dispose(); } - } diff --git a/src/features/UpdatePowerShell.ts b/src/features/UpdatePowerShell.ts index adc5194912..acd3434fb5 100644 --- a/src/features/UpdatePowerShell.ts +++ b/src/features/UpdatePowerShell.ts @@ -19,8 +19,10 @@ interface IUpdateMessageItem extends vscode.MessageItem { export class UpdatePowerShell { private static LTSBuildInfoURL = "https://aka.ms/pwsh-buildinfo-lts"; private static StableBuildInfoURL = "https://aka.ms/pwsh-buildinfo-stable"; - private static PreviewBuildInfoURL = "https://aka.ms/pwsh-buildinfo-preview"; - private static GitHubWebReleaseURL = "https://github.com/PowerShell/PowerShell/releases/tag/"; + private static PreviewBuildInfoURL = + "https://aka.ms/pwsh-buildinfo-preview"; + private static GitHubWebReleaseURL = + "https://github.com/PowerShell/PowerShell/releases/tag/"; private static promptOptions: IUpdateMessageItem[] = [ { id: 0, @@ -40,7 +42,8 @@ export class UpdatePowerShell { constructor( private sessionSettings: Settings, private logger: ILogger, - versionDetails: IPowerShellVersionDetails) { + versionDetails: IPowerShellVersionDetails, + ) { // We use the commit field as it's like // '7.3.0-preview.3-508-g07175ae0ff8eb7306fe0b0fc7d...' which translates // to SemVer. The version handler in PSES handles Windows PowerShell and @@ -51,20 +54,26 @@ export class UpdatePowerShell { private shouldCheckForUpdate(): boolean { // Respect user setting. if (!this.sessionSettings.promptToUpdatePowerShell) { - this.logger.writeDebug("Setting 'promptToUpdatePowerShell' was false."); + this.logger.writeDebug( + "Setting 'promptToUpdatePowerShell' was false.", + ); return false; } // Respect environment configuration. if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "off") { - this.logger.writeDebug("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'."); + this.logger.writeDebug( + "Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.", + ); return false; } // Skip prompting when using Windows PowerShell for now. if (this.localVersion.compare("6.0.0") === -1) { // TODO: Maybe we should announce PowerShell Core? - this.logger.writeDebug("Not prompting to update Windows PowerShell."); + this.logger.writeDebug( + "Not prompting to update Windows PowerShell.", + ); return false; } @@ -78,7 +87,9 @@ export class UpdatePowerShell { // Skip if PowerShell is self-built, that is, this contains a commit hash. if (commit.length >= 40) { - this.logger.writeDebug("Not prompting to update development build."); + this.logger.writeDebug( + "Not prompting to update development build.", + ); return false; } @@ -106,7 +117,9 @@ export class UpdatePowerShell { // "ReleaseTag": "v7.2.7" // } const data = await response.json(); - this.logger.writeDebug(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`); + this.logger.writeDebug( + `Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`, + ); return data.ReleaseTag; } @@ -120,14 +133,18 @@ export class UpdatePowerShell { if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "lts") { // Only check for update to LTS. this.logger.writeDebug("Checking for LTS update..."); - const tag = await this.getRemoteVersion(UpdatePowerShell.LTSBuildInfoURL); + const tag = await this.getRemoteVersion( + UpdatePowerShell.LTSBuildInfoURL, + ); if (tag != undefined) { tags.push(tag); } } else { // Check for update to stable. this.logger.writeDebug("Checking for stable update..."); - const tag = await this.getRemoteVersion(UpdatePowerShell.StableBuildInfoURL); + const tag = await this.getRemoteVersion( + UpdatePowerShell.StableBuildInfoURL, + ); if (tag != undefined) { tags.push(tag); } @@ -135,7 +152,9 @@ export class UpdatePowerShell { // Also check for a preview update. if (this.localVersion.prerelease.length > 0) { this.logger.writeDebug("Checking for preview update..."); - const tag = await this.getRemoteVersion(UpdatePowerShell.PreviewBuildInfoURL); + const tag = await this.getRemoteVersion( + UpdatePowerShell.PreviewBuildInfoURL, + ); if (tag != undefined) { tags.push(tag); } @@ -161,23 +180,30 @@ export class UpdatePowerShell { } } catch (err) { // Best effort. This probably failed to fetch the data from GitHub. - this.logger.writeWarning(err instanceof Error ? err.message : "unknown"); + this.logger.writeWarning( + err instanceof Error ? err.message : "unknown", + ); } } private async openReleaseInBrowser(tag: string): Promise { - const url = vscode.Uri.parse(UpdatePowerShell.GitHubWebReleaseURL + tag); + const url = vscode.Uri.parse( + UpdatePowerShell.GitHubWebReleaseURL + tag, + ); await vscode.env.openExternal(url); } private async promptToUpdate(tag: string): Promise { const releaseVersion = new SemVer(tag); - this.logger.write(`Prompting to update PowerShell v${this.localVersion.version} to v${releaseVersion.version}.`); + this.logger.write( + `Prompting to update PowerShell v${this.localVersion.version} to v${releaseVersion.version}.`, + ); const result = await vscode.window.showInformationMessage( `PowerShell v${this.localVersion.version} is out-of-date. The latest version is v${releaseVersion.version}. Would you like to open the GitHub release in your browser?`, - ...UpdatePowerShell.promptOptions); + ...UpdatePowerShell.promptOptions, + ); // If the user cancels the notification. if (!result) { @@ -185,22 +211,29 @@ export class UpdatePowerShell { return; } - this.logger.writeDebug(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`); + this.logger.writeDebug( + `User said '${UpdatePowerShell.promptOptions[result.id].title}'.`, + ); switch (result.id) { - // Yes - case 0: - await this.openReleaseInBrowser(tag); - break; + // Yes + case 0: + await this.openReleaseInBrowser(tag); + break; // Not Now - case 1: - break; + case 1: + break; // Don't Show Again - case 2: - await changeSetting("promptToUpdatePowerShell", false, true, this.logger); - break; - default: - break; + case 2: + await changeSetting( + "promptToUpdatePowerShell", + false, + true, + this.logger, + ); + break; + default: + break; } } } diff --git a/src/languageClientConsumer.ts b/src/languageClientConsumer.ts index 3106d86cc4..e161f5a34e 100644 --- a/src/languageClientConsumer.ts +++ b/src/languageClientConsumer.ts @@ -14,7 +14,9 @@ export abstract class LanguageClientConsumer { // This is called in the session manager when the client is started (so we // can wait for that). It's what actually resolves the promise. - public static onLanguageClientStarted(languageClient: LanguageClient): void { + public static onLanguageClientStarted( + languageClient: LanguageClient, + ): void { // It should have been created earlier, but if not, create and resolve it. this.languageClientPromise ??= Promise.resolve(languageClient); this.getLanguageClientResolve?.(languageClient); @@ -34,7 +36,8 @@ export abstract class LanguageClientConsumer { LanguageClientConsumer.languageClientPromise?.catch(() => { LanguageClientConsumer.languageClientPromise = undefined; }); - LanguageClientConsumer.languageClientPromise ??= LanguageClientConsumer.createLanguageClientPromise(); + LanguageClientConsumer.languageClientPromise ??= + LanguageClientConsumer.createLanguageClientPromise(); return LanguageClientConsumer.languageClientPromise; } @@ -45,22 +48,29 @@ export abstract class LanguageClientConsumer { { location: ProgressLocation.Notification, title: "Please wait, starting PowerShell Extension Terminal...", - cancellable: true + cancellable: true, }, (_progress, token) => { token.onCancellationRequested(() => { - void window.showErrorMessage("Cancelled PowerShell Extension Terminal start-up."); + void window.showErrorMessage( + "Cancelled PowerShell Extension Terminal start-up.", + ); }); // The real promise! - return new Promise( - (resolve, reject) => { - // Store the resolve function to be called in resetLanguageClient. - LanguageClientConsumer.getLanguageClientResolve = resolve; - // Reject the promise if the operation is cancelled. - token.onCancellationRequested(() => { reject(new Error("Cancelled PowerShell Extension Terminal start-up.")); }); - } - ); - }); + return new Promise((resolve, reject) => { + // Store the resolve function to be called in resetLanguageClient. + LanguageClientConsumer.getLanguageClientResolve = resolve; + // Reject the promise if the operation is cancelled. + token.onCancellationRequested(() => { + reject( + new Error( + "Cancelled PowerShell Extension Terminal start-up.", + ), + ); + }); + }); + }, + ); } } diff --git a/src/logging.ts b/src/logging.ts index 37fc74072f..4a9d9fa54d 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,39 +1,60 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { type LogOutputChannel, LogLevel, window, type Event } from "vscode"; +import { LogLevel, window, type Event, type LogOutputChannel } from "vscode"; /** Interface for logging operations. New features should use this interface for the "type" of logger. * This will allow for easy mocking of the logger during unit tests. */ export interface ILogger { write(message: string, ...additionalMessages: string[]): void; - writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise; + writeAndShowInformation( + message: string, + ...additionalMessages: string[] + ): Promise; writeTrace(message: string, ...additionalMessages: string[]): void; writeDebug(message: string, ...additionalMessages: string[]): void; writeWarning(message: string, ...additionalMessages: string[]): void; - writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise; + writeAndShowWarning( + message: string, + ...additionalMessages: string[] + ): Promise; writeError(message: string, ...additionalMessages: string[]): void; - writeAndShowError(message: string, ...additionalMessages: string[]): Promise; + writeAndShowError( + message: string, + ...additionalMessages: string[] + ): Promise; writeAndShowErrorWithActions( message: string, - actions: { prompt: string; action: (() => Promise) | undefined }[]): Promise; + actions: { + prompt: string; + action: (() => Promise) | undefined; + }[], + ): Promise; } export class Logger implements ILogger { // Log output channel handles all the verbosity management so we don't have to. private logChannel: LogOutputChannel; - public get logLevel(): LogLevel { return this.logChannel.logLevel;} + public get logLevel(): LogLevel { + return this.logChannel.logLevel; + } constructor(logChannel?: LogOutputChannel) { - this.logChannel = logChannel ?? window.createOutputChannel("PowerShell", {log: true}); + this.logChannel = + logChannel ?? + window.createOutputChannel("PowerShell", { log: true }); } public dispose(): void { this.logChannel.dispose(); } - private writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]): void { + private writeAtLevel( + logLevel: LogLevel, + message: string, + ...additionalMessages: string[] + ): void { if (logLevel >= this.logLevel) { void this.writeLine(message, logLevel); @@ -47,10 +68,17 @@ export class Logger implements ILogger { this.writeAtLevel(LogLevel.Info, message, ...additionalMessages); } - public async writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise { + public async writeAndShowInformation( + message: string, + ...additionalMessages: string[] + ): Promise { this.write(message, ...additionalMessages); - const selection = await window.showInformationMessage(message, "Show Logs", "Okay"); + const selection = await window.showInformationMessage( + message, + "Show Logs", + "Okay", + ); if (selection === "Show Logs") { this.showLogPanel(); } @@ -64,11 +92,17 @@ export class Logger implements ILogger { this.writeAtLevel(LogLevel.Debug, message, ...additionalMessages); } - public writeWarning(message: string, ...additionalMessages: string[]): void { + public writeWarning( + message: string, + ...additionalMessages: string[] + ): void { this.writeAtLevel(LogLevel.Warning, message, ...additionalMessages); } - public async writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise { + public async writeAndShowWarning( + message: string, + ...additionalMessages: string[] + ): Promise { this.writeWarning(message, ...additionalMessages); const selection = await window.showWarningMessage(message, "Show Logs"); @@ -81,7 +115,10 @@ export class Logger implements ILogger { this.writeAtLevel(LogLevel.Error, message, ...additionalMessages); } - public async writeAndShowError(message: string, ...additionalMessages: string[]): Promise { + public async writeAndShowError( + message: string, + ...additionalMessages: string[] + ): Promise { this.writeError(message, ...additionalMessages); const choice = await window.showErrorMessage(message, "Show Logs"); @@ -92,12 +129,21 @@ export class Logger implements ILogger { public async writeAndShowErrorWithActions( message: string, - actions: { prompt: string; action: (() => Promise) | undefined }[]): Promise { + actions: { + prompt: string; + action: (() => Promise) | undefined; + }[], + ): Promise { this.writeError(message); const fullActions = [ ...actions, - { prompt: "Show Logs", action: (): void => { this.showLogPanel(); } }, + { + prompt: "Show Logs", + action: (): void => { + this.showLogPanel(); + }, + }, ]; const actionKeys: string[] = fullActions.map((action) => action.prompt); @@ -105,7 +151,7 @@ export class Logger implements ILogger { const choice = await window.showErrorMessage(message, ...actionKeys); if (choice) { for (const action of fullActions) { - if (choice === action.prompt && action.action !== undefined ) { + if (choice === action.prompt && action.action !== undefined) { await action.action(); return; } @@ -117,16 +163,32 @@ export class Logger implements ILogger { this.logChannel.show(); } - private async writeLine(message: string, level: LogLevel = LogLevel.Info): Promise { + private async writeLine( + message: string, + level: LogLevel = LogLevel.Info, + ): Promise { return new Promise((resolve) => { switch (level) { - case LogLevel.Off: break; - case LogLevel.Trace: this.logChannel.trace(message); break; - case LogLevel.Debug: this.logChannel.debug(message); break; - case LogLevel.Info: this.logChannel.info(message); break; - case LogLevel.Warning: this.logChannel.warn(message); break; - case LogLevel.Error: this.logChannel.error(message); break; - default: this.logChannel.appendLine(message); break; + case LogLevel.Off: + break; + case LogLevel.Trace: + this.logChannel.trace(message); + break; + case LogLevel.Debug: + this.logChannel.debug(message); + break; + case LogLevel.Info: + this.logChannel.info(message); + break; + case LogLevel.Warning: + this.logChannel.warn(message); + break; + case LogLevel.Error: + this.logChannel.error(message); + break; + default: + this.logChannel.appendLine(message); + break; } resolve(); }); @@ -140,7 +202,9 @@ export class Logger implements ILogger { export class LanguageClientOutputChannelAdapter implements LogOutputChannel { private _channel: LogOutputChannel | undefined; private get channel(): LogOutputChannel { - this._channel ??= window.createOutputChannel(this.channelName, {log: true}); + this._channel ??= window.createOutputChannel(this.channelName, { + log: true, + }); return this._channel; } @@ -152,9 +216,14 @@ export class LanguageClientOutputChannelAdapter implements LogOutputChannel { */ constructor( private channelName: string, - private parser: (message: string) => [string, LogLevel] | undefined = LanguageClientOutputChannelAdapter.omnisharpLspParser.bind(this) - ) { - } + private parser: ( + message: string, + ) => + | [string, LogLevel] + | undefined = LanguageClientOutputChannelAdapter.omnisharpLspParser.bind( + this, + ), + ) {} public appendLine(message: string): void { this.append(message); @@ -162,12 +231,17 @@ export class LanguageClientOutputChannelAdapter implements LogOutputChannel { public append(message: string): void { const parseResult = this.parser(message); - if (parseResult !== undefined) {this.sendLogMessage(...parseResult);} + if (parseResult !== undefined) { + this.sendLogMessage(...parseResult); + } } /** Converts from Omnisharp logs since middleware for LogMessage does not currently exist **/ public static omnisharpLspParser(message: string): [string, LogLevel] { - const logLevelMatch = /^\[(?Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?.+)/.exec(message); + const logLevelMatch = + /^\[(?Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?.+)/.exec( + message, + ); const logLevel: LogLevel = logLevelMatch?.groups?.level ? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel] : LogLevel.Info; @@ -178,24 +252,24 @@ export class LanguageClientOutputChannelAdapter implements LogOutputChannel { protected sendLogMessage(message: string, level: LogLevel): void { switch (level) { - case LogLevel.Trace: - this.channel.trace(message); - break; - case LogLevel.Debug: - this.channel.debug(message); - break; - case LogLevel.Info: - this.channel.info(message); - break; - case LogLevel.Warning: - this.channel.warn(message); - break; - case LogLevel.Error: - this.channel.error(message); - break; - default: - this.channel.error("!UNKNOWN LOG LEVEL!: " + message); - break; + case LogLevel.Trace: + this.channel.trace(message); + break; + case LogLevel.Debug: + this.channel.debug(message); + break; + case LogLevel.Info: + this.channel.info(message); + break; + case LogLevel.Warning: + this.channel.warn(message); + break; + case LogLevel.Error: + this.channel.error(message); + break; + default: + this.channel.error("!UNKNOWN LOG LEVEL!: " + message); + break; } } @@ -250,9 +324,12 @@ export class LanguageClientOutputChannelAdapter implements LogOutputChannel { /** Special parsing for PowerShell Editor Services LSP messages since the LogLevel cannot be read due to vscode * LanguageClient Limitations (https://github.com/microsoft/vscode-languageserver-node/issues/1116) - */ + */ export function PsesParser(message: string): [string, LogLevel] { - const logLevelMatch = /^<(?Trace|Debug|Info|Warning|Error)>(?.+)/.exec(message); + const logLevelMatch = + /^<(?Trace|Debug|Info|Warning|Error)>(?.+)/.exec( + message, + ); const logLevel: LogLevel = logLevelMatch?.groups?.level ? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel] : LogLevel.Info; @@ -263,7 +340,8 @@ export function PsesParser(message: string): [string, LogLevel] { /** Lsp Trace Parser that does some additional parsing and formatting to make it look nicer */ export function LspTraceParser(message: string): [string, LogLevel] { - let [parsedMessage, level] = LanguageClientOutputChannelAdapter.omnisharpLspParser(message); + let [parsedMessage, level] = + LanguageClientOutputChannelAdapter.omnisharpLspParser(message); if (parsedMessage.startsWith("Sending ")) { parsedMessage = parsedMessage.replace("Sending", "➡️"); level = LogLevel.Debug; @@ -272,8 +350,9 @@ export function LspTraceParser(message: string): [string, LogLevel] { parsedMessage = parsedMessage.replace("Received", "⬅️"); level = LogLevel.Debug; } - if (parsedMessage.startsWith("Params:") - || parsedMessage.startsWith("Result:") + if ( + parsedMessage.startsWith("Params:") || + parsedMessage.startsWith("Result:") ) { level = LogLevel.Trace; } diff --git a/src/platform.ts b/src/platform.ts index b4b032e204..76ca5992ed 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -4,12 +4,16 @@ import * as os from "os"; import * as path from "path"; import * as process from "process"; -import vscode = require("vscode"); +import untildify from "untildify"; import { integer } from "vscode-languageserver-protocol"; import type { ILogger } from "./logging"; -import { changeSetting, getSettings, type PowerShellAdditionalExePathSettings } from "./settings"; +import { + changeSetting, + getSettings, + type PowerShellAdditionalExePathSettings, +} from "./settings"; import * as utils from "./utils"; -import untildify from "untildify"; +import vscode = require("vscode"); const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; @@ -57,11 +61,12 @@ export function getPlatformDetails(): IPlatformDetails { operatingSystem = OperatingSystem.Linux; } - const isProcess64Bit = (process.arch === "x64" || process.arch === "arm64"); + const isProcess64Bit = process.arch === "x64" || process.arch === "arm64"; return { operatingSystem, - isOS64Bit: isProcess64Bit || (process.env.PROCESSOR_ARCHITEW6432 !== undefined), + isOS64Bit: + isProcess64Bit || process.env.PROCESSOR_ARCHITEW6432 !== undefined, isProcess64Bit, }; } @@ -89,12 +94,15 @@ export class PowerShellExeFinder { private platformDetails: IPlatformDetails, // Additional configured PowerShells private additionalPowerShellExes: PowerShellAdditionalExePathSettings, - private logger?: ILogger) { } + private logger?: ILogger, + ) {} /** * Returns the first available PowerShell executable found in the search order. */ - public async getFirstAvailablePowerShellInstallation(): Promise { + public async getFirstAvailablePowerShellInstallation(): Promise< + IPowerShellExeDetails | undefined + > { for await (const pwsh of this.enumeratePowerShellInstallations()) { return pwsh; } @@ -104,7 +112,9 @@ export class PowerShellExeFinder { /** * Get an array of all PowerShell executables found when searching for PowerShell installations. */ - public async getAllAvailablePowerShellInstallations(): Promise { + public async getAllAvailablePowerShellInstallations(): Promise< + IPowerShellExeDetails[] + > { const array: IPowerShellExeDetails[] = []; for await (const pwsh of this.enumeratePowerShellInstallations()) { array.push(pwsh); @@ -116,7 +126,9 @@ export class PowerShellExeFinder { * Fixes PowerShell paths when Windows PowerShell is set to the non-native bitness. * @param configuredPowerShellPath the PowerShell path configured by the user. */ - public fixWindowsPowerShellPath(configuredPowerShellPath: string): string | undefined { + public fixWindowsPowerShellPath( + configuredPowerShellPath: string, + ): string | undefined { const altWinPS = this.findWinPS({ useAlternateBitness: true }); if (!altWinPS) { @@ -124,7 +136,8 @@ export class PowerShellExeFinder { } const lowerAltWinPSPath = altWinPS.exePath.toLocaleLowerCase(); - const lowerConfiguredPath = configuredPowerShellPath.toLocaleLowerCase(); + const lowerConfiguredPath = + configuredPowerShellPath.toLocaleLowerCase(); if (lowerConfiguredPath === lowerAltWinPSPath) { return this.findWinPS()?.exePath; @@ -142,7 +155,7 @@ export class PowerShellExeFinder { public async *enumeratePowerShellInstallations(): AsyncIterable { // Get the default PowerShell installations first for await (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) { - if (defaultPwsh && await defaultPwsh.exists()) { + if (defaultPwsh && (await defaultPwsh.exists())) { yield defaultPwsh; } } @@ -157,9 +170,17 @@ export class PowerShellExeFinder { this.logger?.writeWarning(message); if (!getSettings().suppressAdditionalExeNotFoundWarning) { - const selection = await vscode.window.showWarningMessage(message, "Don't Show Again"); + const selection = await vscode.window.showWarningMessage( + message, + "Don't Show Again", + ); if (selection !== undefined) { - await changeSetting("suppressAdditionalExeNotFoundWarning", true, true, this.logger); + await changeSetting( + "suppressAdditionalExeNotFoundWarning", + true, + true, + this.logger, + ); } } } @@ -172,28 +193,32 @@ export class PowerShellExeFinder { * which will check whether the executable exists. * TODO: We really need to define the order in which we search for stable/LTS/preview/daily */ - private async *enumerateDefaultPowerShellInstallations(): AsyncIterable { + private async *enumerateDefaultPowerShellInstallations(): AsyncIterable< + IPossiblePowerShellExe | undefined + > { // Find PSCore stable first yield this.findPSCoreStable(); switch (this.platformDetails.operatingSystem) { - case OperatingSystem.Linux: - // On Linux, find the snap - yield this.findPSCoreStableSnap(); - break; - - case OperatingSystem.Windows: - // Windows may have a 32-bit pwsh.exe - yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true }); - // Also look for the MSIX/UWP installation - yield await this.findPSCoreMsix(); - break; - - case OperatingSystem.MacOS: - // On MacOS, find the Homebrew installations - yield this.findPSCoreHomebrewStable(); - yield this.findPSCoreHomebrewLTS(); - break; + case OperatingSystem.Linux: + // On Linux, find the snap + yield this.findPSCoreStableSnap(); + break; + + case OperatingSystem.Windows: + // Windows may have a 32-bit pwsh.exe + yield this.findPSCoreWindowsInstallation({ + useAlternateBitness: true, + }); + // Also look for the MSIX/UWP installation + yield await this.findPSCoreMsix(); + break; + + case OperatingSystem.MacOS: + // On MacOS, find the Homebrew installations + yield this.findPSCoreHomebrewStable(); + yield this.findPSCoreHomebrewLTS(); + break; } // Look for the .NET global tool @@ -205,32 +230,35 @@ export class PowerShellExeFinder { yield this.findPSCorePreview(); switch (this.platformDetails.operatingSystem) { - // On Linux, there might be a preview snap - case OperatingSystem.Linux: - yield this.findPSCorePreviewSnap(); - break; + // On Linux, there might be a preview snap + case OperatingSystem.Linux: + yield this.findPSCorePreviewSnap(); + break; - case OperatingSystem.Windows: - // Find a preview MSIX - yield this.findPSCoreMsix({ findPreview: true }); + case OperatingSystem.Windows: + // Find a preview MSIX + yield this.findPSCoreMsix({ findPreview: true }); - // Look for pwsh-preview with the opposite bitness - yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true, findPreview: true }); + // Look for pwsh-preview with the opposite bitness + yield this.findPSCoreWindowsInstallation({ + useAlternateBitness: true, + findPreview: true, + }); - // Finally, get Windows PowerShell + // Finally, get Windows PowerShell - // Get the natural Windows PowerShell for the process bitness - yield this.findWinPS(); + // Get the natural Windows PowerShell for the process bitness + yield this.findWinPS(); - // Get the alternate bitness Windows PowerShell - yield this.findWinPS({ useAlternateBitness: true }); + // Get the alternate bitness Windows PowerShell + yield this.findWinPS({ useAlternateBitness: true }); - break; + break; - case OperatingSystem.MacOS: - // On MacOS, find the Homebrew preview - yield this.findPSCoreHomebrewPreview(); - break; + case OperatingSystem.MacOS: + // On MacOS, find the Homebrew preview + yield this.findPSCoreHomebrewPreview(); + break; } // Look for PSCore daily @@ -243,16 +271,23 @@ export class PowerShellExeFinder { */ public async *enumerateAdditionalPowerShellInstallations(): AsyncIterable { for (const versionName in this.additionalPowerShellExes) { - if (Object.prototype.hasOwnProperty.call(this.additionalPowerShellExes, versionName)) { - let exePath: string | undefined = utils.stripQuotePair(this.additionalPowerShellExes[versionName]); + if ( + Object.prototype.hasOwnProperty.call( + this.additionalPowerShellExes, + versionName, + ) + ) { + let exePath: string | undefined = utils.stripQuotePair( + this.additionalPowerShellExes[versionName], + ); if (!exePath) { continue; } exePath = untildify(exePath); - const args: [string, undefined, boolean, boolean] + const args: [string, undefined, boolean, boolean] = // Must be a tuple type and is suppressing the warning - = [versionName, undefined, true, true]; + [versionName, undefined, true, true]; // Always search for what the user gave us first, but with the warning // suppressed so we can display it after all possibilities are exhausted @@ -263,12 +298,24 @@ export class PowerShellExeFinder { } // Also search for `pwsh[.exe]` and `powershell[.exe]` if missing - if (this.platformDetails.operatingSystem === OperatingSystem.Windows) { + if ( + this.platformDetails.operatingSystem === + OperatingSystem.Windows + ) { // Handle Windows where '.exe' and 'powershell' are things - if (!exePath.endsWith("pwsh.exe") && !exePath.endsWith("powershell.exe")) { - if (exePath.endsWith("pwsh") || exePath.endsWith("powershell")) { + if ( + !exePath.endsWith("pwsh.exe") && + !exePath.endsWith("powershell.exe") + ) { + if ( + exePath.endsWith("pwsh") || + exePath.endsWith("powershell") + ) { // Add extension if that was missing - pwsh = new PossiblePowerShellExe(exePath + ".exe", ...args); + pwsh = new PossiblePowerShellExe( + exePath + ".exe", + ...args, + ); if (await pwsh.exists()) { yield pwsh; continue; @@ -276,12 +323,18 @@ export class PowerShellExeFinder { } // Also add full exe names (this isn't an else just in case // the folder was named "pwsh" or "powershell") - pwsh = new PossiblePowerShellExe(path.join(exePath, "pwsh.exe"), ...args); + pwsh = new PossiblePowerShellExe( + path.join(exePath, "pwsh.exe"), + ...args, + ); if (await pwsh.exists()) { yield pwsh; continue; } - pwsh = new PossiblePowerShellExe(path.join(exePath, "powershell.exe"), ...args); + pwsh = new PossiblePowerShellExe( + path.join(exePath, "powershell.exe"), + ...args, + ); if (await pwsh.exists()) { yield pwsh; continue; @@ -289,7 +342,10 @@ export class PowerShellExeFinder { } } else if (!exePath.endsWith("pwsh")) { // Always just 'pwsh' on non-Windows - pwsh = new PossiblePowerShellExe(path.join(exePath, "pwsh"), ...args); + pwsh = new PossiblePowerShellExe( + path.join(exePath, "pwsh"), + ...args, + ); if (await pwsh.exists()) { yield pwsh; continue; @@ -297,127 +353,191 @@ export class PowerShellExeFinder { } // If we're still being iterated over, no permutation of the given path existed so yield an object with the warning unsuppressed - yield new PossiblePowerShellExe(exePath, versionName, false, undefined, false); + yield new PossiblePowerShellExe( + exePath, + versionName, + false, + undefined, + false, + ); } } } - private async findPSCoreStable(): Promise { + private async findPSCoreStable(): Promise< + IPossiblePowerShellExe | undefined + > { switch (this.platformDetails.operatingSystem) { - case OperatingSystem.Linux: - return new PossiblePowerShellExe(LinuxExePath, "PowerShell"); + case OperatingSystem.Linux: + return new PossiblePowerShellExe(LinuxExePath, "PowerShell"); - case OperatingSystem.MacOS: - return new PossiblePowerShellExe(MacOSExePath, "PowerShell"); + case OperatingSystem.MacOS: + return new PossiblePowerShellExe(MacOSExePath, "PowerShell"); - case OperatingSystem.Windows: - return await this.findPSCoreWindowsInstallation(); + case OperatingSystem.Windows: + return await this.findPSCoreWindowsInstallation(); - case OperatingSystem.Unknown: - return undefined; + case OperatingSystem.Unknown: + return undefined; } } - private async findPSCorePreview(): Promise { + private async findPSCorePreview(): Promise< + IPossiblePowerShellExe | undefined + > { switch (this.platformDetails.operatingSystem) { - case OperatingSystem.Linux: - return new PossiblePowerShellExe(LinuxPreviewExePath, "PowerShell Preview"); - - case OperatingSystem.MacOS: - return new PossiblePowerShellExe(MacOSPreviewExePath, "PowerShell Preview"); - - case OperatingSystem.Windows: - return await this.findPSCoreWindowsInstallation({ findPreview: true }); - - case OperatingSystem.Unknown: - return undefined; + case OperatingSystem.Linux: + return new PossiblePowerShellExe( + LinuxPreviewExePath, + "PowerShell Preview", + ); + + case OperatingSystem.MacOS: + return new PossiblePowerShellExe( + MacOSPreviewExePath, + "PowerShell Preview", + ); + + case OperatingSystem.Windows: + return await this.findPSCoreWindowsInstallation({ + findPreview: true, + }); + + case OperatingSystem.Unknown: + return undefined; } } /** - * If the daily was installed via 'https://aka.ms/install-powershell.ps1', then - * this is the default installation location: - * - * if ($IsWinEnv) { - * $Destination = "$env:LOCALAPPDATA\Microsoft\powershell" - * } else { - * $Destination = "~/.powershell" - * } - * - * if ($Daily) { - * $Destination = "${Destination}-daily" - * } - * - * TODO: Remove this after the daily is officially no longer supported. - */ + * If the daily was installed via 'https://aka.ms/install-powershell.ps1', then + * this is the default installation location: + * + * if ($IsWinEnv) { + * $Destination = "$env:LOCALAPPDATA\Microsoft\powershell" + * } else { + * $Destination = "~/.powershell" + * } + * + * if ($Daily) { + * $Destination = "${Destination}-daily" + * } + * + * TODO: Remove this after the daily is officially no longer supported. + */ private findPSCoreDaily(): IPossiblePowerShellExe | undefined { switch (this.platformDetails.operatingSystem) { - case OperatingSystem.Linux: - case OperatingSystem.MacOS: { - const exePath = path.join(os.homedir(), ".powershell-daily", "pwsh"); - return new PossiblePowerShellExe(exePath, "PowerShell Daily"); - } + case OperatingSystem.Linux: + case OperatingSystem.MacOS: { + const exePath = path.join( + os.homedir(), + ".powershell-daily", + "pwsh", + ); + return new PossiblePowerShellExe(exePath, "PowerShell Daily"); + } - case OperatingSystem.Windows: { - // We can't proceed if there's no LOCALAPPDATA path - if (!process.env.LOCALAPPDATA) { - return undefined; + case OperatingSystem.Windows: { + // We can't proceed if there's no LOCALAPPDATA path + if (!process.env.LOCALAPPDATA) { + return undefined; + } + const exePath = path.join( + process.env.LOCALAPPDATA, + "Microsoft", + "powershell-daily", + "pwsh.exe", + ); + return new PossiblePowerShellExe(exePath, "PowerShell Daily"); } - const exePath = path.join(process.env.LOCALAPPDATA, "Microsoft", "powershell-daily", "pwsh.exe"); - return new PossiblePowerShellExe(exePath, "PowerShell Daily"); - } - case OperatingSystem.Unknown: - return undefined; + case OperatingSystem.Unknown: + return undefined; } } // The Homebrew installations of PowerShell on Apple Silicon are no longer in the default PATH. private findPSCoreHomebrewStable(): IPossiblePowerShellExe { - return new PossiblePowerShellExe(MacOSHomebrewExePath, "PowerShell (Homebrew)"); + return new PossiblePowerShellExe( + MacOSHomebrewExePath, + "PowerShell (Homebrew)", + ); } private findPSCoreHomebrewLTS(): IPossiblePowerShellExe { - return new PossiblePowerShellExe(MacOSHomebrewLTSExePath, "PowerShell LTS (Homebrew)"); + return new PossiblePowerShellExe( + MacOSHomebrewLTSExePath, + "PowerShell LTS (Homebrew)", + ); } private findPSCoreHomebrewPreview(): IPossiblePowerShellExe { - return new PossiblePowerShellExe(MacOSHomebrewPreviewExePath, "PowerShell Preview (Homebrew)"); + return new PossiblePowerShellExe( + MacOSHomebrewPreviewExePath, + "PowerShell Preview (Homebrew)", + ); } private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { - const exeName: string = this.platformDetails.operatingSystem === OperatingSystem.Windows - ? "pwsh.exe" - : "pwsh"; - - const dotnetGlobalToolExePath: string = path.join(os.homedir(), ".dotnet", "tools", exeName); + const exeName: string = + this.platformDetails.operatingSystem === OperatingSystem.Windows + ? "pwsh.exe" + : "pwsh"; + + const dotnetGlobalToolExePath: string = path.join( + os.homedir(), + ".dotnet", + "tools", + exeName, + ); // The dotnet installed version of PowerShell does not support proper argument parsing, and so it fails with our multi-line startup banner. - return new PossiblePowerShellExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool", undefined, false); + return new PossiblePowerShellExe( + dotnetGlobalToolExePath, + ".NET Core PowerShell Global Tool", + undefined, + false, + ); } - private async findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise { + private async findPSCoreMsix({ + findPreview, + }: { findPreview?: boolean } = {}): Promise< + IPossiblePowerShellExe | undefined + > { // We can't proceed if there's no LOCALAPPDATA path if (!process.env.LOCALAPPDATA) { return undefined; } // Find the base directory for MSIX application exe shortcuts - const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); + const msixAppDir = path.join( + process.env.LOCALAPPDATA, + "Microsoft", + "WindowsApps", + ); - if (!await utils.checkIfDirectoryExists(msixAppDir)) { + if (!(await utils.checkIfDirectoryExists(msixAppDir))) { return undefined; } // Define whether we're looking for the preview or the stable const { pwshMsixDirRegex, pwshMsixName } = findPreview - ? { pwshMsixDirRegex: PowerShellExeFinder.PwshPreviewMsixRegex, pwshMsixName: "PowerShell Preview (Store)" } - : { pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, pwshMsixName: "PowerShell (Store)" }; + ? { + pwshMsixDirRegex: PowerShellExeFinder.PwshPreviewMsixRegex, + pwshMsixName: "PowerShell Preview (Store)", + } + : { + pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, + pwshMsixName: "PowerShell (Store)", + }; // We should find only one such application, so return on the first one for (const name of await utils.readDirectory(msixAppDir)) { if (pwshMsixDirRegex.test(name)) { - return new PossiblePowerShellExe(path.join(msixAppDir, name, "pwsh.exe"), pwshMsixName); + return new PossiblePowerShellExe( + path.join(msixAppDir, name, "pwsh.exe"), + pwshMsixName, + ); } } @@ -430,29 +550,41 @@ export class PowerShellExeFinder { } private findPSCorePreviewSnap(): IPossiblePowerShellExe { - return new PossiblePowerShellExe(SnapPreviewExePath, "PowerShell Preview Snap"); + return new PossiblePowerShellExe( + SnapPreviewExePath, + "PowerShell Preview Snap", + ); } - private async findPSCoreWindowsInstallation( - { useAlternateBitness = false, findPreview = false }: - { useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise { - - const programFilesPath = this.getProgramFilesPath({ useAlternateBitness }); + private async findPSCoreWindowsInstallation({ + useAlternateBitness = false, + findPreview = false, + }: { useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise< + IPossiblePowerShellExe | undefined + > { + const programFilesPath = this.getProgramFilesPath({ + useAlternateBitness, + }); if (!programFilesPath) { return undefined; } - const powerShellInstallBaseDir = path.join(programFilesPath, "PowerShell"); + const powerShellInstallBaseDir = path.join( + programFilesPath, + "PowerShell", + ); // Ensure the base directory exists - if (!await utils.checkIfDirectoryExists(powerShellInstallBaseDir)) { + if (!(await utils.checkIfDirectoryExists(powerShellInstallBaseDir))) { return undefined; } let highestSeenVersion = -1; let pwshExePath: string | undefined; - for (const item of await utils.readDirectory(powerShellInstallBaseDir)) { + for (const item of await utils.readDirectory( + powerShellInstallBaseDir, + )) { let currentVersion = -1; if (findPreview) { // We are looking for something like "7-preview" @@ -490,8 +622,12 @@ export class PowerShellExeFinder { } // Now look for the file - const exePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); - if (!await utils.checkIfFileExists(exePath)) { + const exePath = path.join( + powerShellInstallBaseDir, + item, + "pwsh.exe", + ); + if (!(await utils.checkIfFileExists(exePath))) { continue; } @@ -509,25 +645,40 @@ export class PowerShellExeFinder { const preview: string = findPreview ? " Preview" : ""; - return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview} ${bitness}`); + return new PossiblePowerShellExe( + pwshExePath, + `PowerShell${preview} ${bitness}`, + ); } - private findWinPS({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): IPossiblePowerShellExe | undefined { - + private findWinPS({ + useAlternateBitness = false, + }: { useAlternateBitness?: boolean } = {}): + | IPossiblePowerShellExe + | undefined { // 32-bit OSes only have one WinPS on them if (!this.platformDetails.isOS64Bit && useAlternateBitness) { return undefined; } - let winPS = useAlternateBitness ? this.alternateBitnessWinPS : this.winPS; + let winPS = useAlternateBitness + ? this.alternateBitnessWinPS + : this.winPS; if (winPS === undefined) { - const systemFolderPath = this.getSystem32Path({ useAlternateBitness }); + const systemFolderPath = this.getSystem32Path({ + useAlternateBitness, + }); if (!systemFolderPath) { return undefined; } - const winPSPath = path.join(systemFolderPath, "WindowsPowerShell", "v1.0", "powershell.exe"); + const winPSPath = path.join( + systemFolderPath, + "WindowsPowerShell", + "v1.0", + "powershell.exe", + ); let displayName: string; if (this.platformDetails.isProcess64Bit) { @@ -554,9 +705,9 @@ export class PowerShellExeFinder { return winPS; } - private getProgramFilesPath( - { useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | undefined { - + private getProgramFilesPath({ + useAlternateBitness = false, + }: { useAlternateBitness?: boolean } = {}): string | undefined { if (!useAlternateBitness) { // Just use the native system bitness return process.env.ProgramFiles; @@ -576,7 +727,9 @@ export class PowerShellExeFinder { return undefined; } - private getSystem32Path({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | undefined { + private getSystem32Path({ + useAlternateBitness = false, + }: { useAlternateBitness?: boolean } = {}): string | undefined { const windir = process.env.windir; if (!windir) { @@ -603,15 +756,19 @@ export class PowerShellExeFinder { } } -export function getWindowsSystemPowerShellPath(systemFolderName: string): string | undefined { +export function getWindowsSystemPowerShellPath( + systemFolderName: string, +): string | undefined { if (process.env.windir === undefined) { return undefined; - } else return path.join( - process.env.windir, - systemFolderName, - "WindowsPowerShell", - "v1.0", - "powershell.exe"); + } else + return path.join( + process.env.windir, + systemFolderName, + "WindowsPowerShell", + "v1.0", + "powershell.exe", + ); } interface IPossiblePowerShellExe extends IPowerShellExeDetails { @@ -625,7 +782,8 @@ class PossiblePowerShellExe implements IPossiblePowerShellExe { public readonly displayName: string, private knownToExist?: boolean, public readonly supportsProperArguments = true, - public readonly suppressWarning = false) { } + public readonly suppressWarning = false, + ) {} public async exists(): Promise { this.knownToExist ??= await utils.checkIfFileExists(this.exePath); diff --git a/src/process.ts b/src/process.ts index 38bba0bc42..0f1503d468 100644 --- a/src/process.ts +++ b/src/process.ts @@ -4,11 +4,11 @@ import cp = require("child_process"); import path = require("path"); import vscode = require("vscode"); +import { promisify } from "util"; import type { ILogger } from "./logging"; +import type { IEditorServicesSessionDetails } from "./session"; import { Settings, validateCwdSetting } from "./settings"; import utils = require("./utils"); -import type { IEditorServicesSessionDetails } from "./session"; -import { promisify } from "util"; export class PowerShellProcess { // This is used to warn the user that the extension is taking longer than expected to startup. @@ -35,24 +35,27 @@ export class PowerShellProcess { private startPsesArgs: string, private sessionFilePath: vscode.Uri, private sessionSettings: Settings, - private devMode = false + private devMode = false, ) { - this.onExitedEmitter = new vscode.EventEmitter(); this.onExited = this.onExitedEmitter.event; this.pidUpdateEmitter = new vscode.EventEmitter(); } - public async start(cancellationToken: vscode.CancellationToken): Promise { - const psesModulePath = - path.resolve( - __dirname, - this.bundledModulesPath, - "PowerShellEditorServices/PowerShellEditorServices.psd1"); + public async start( + cancellationToken: vscode.CancellationToken, + ): Promise { + const psesModulePath = path.resolve( + __dirname, + this.bundledModulesPath, + "PowerShellEditorServices/PowerShellEditorServices.psd1", + ); const featureFlags = this.sessionSettings.developer.featureFlags.length > 0 - ? this.sessionSettings.developer.featureFlags.map((f) => `'${f}'`).join(", ") + ? this.sessionSettings.developer.featureFlags + .map((f) => `'${f}'`) + .join(", ") : ""; this.startPsesArgs += @@ -67,8 +70,8 @@ export class PowerShellProcess { const powerShellArgs: string[] = []; const useLoginShell: boolean = - (utils.isMacOS && this.sessionSettings.startAsLoginShell.osx) - || (utils.isLinux && this.sessionSettings.startAsLoginShell.linux); + (utils.isMacOS && this.sessionSettings.startAsLoginShell.osx) || + (utils.isLinux && this.sessionSettings.startAsLoginShell.linux); if (useLoginShell && this.isLoginShell(this.exePath)) { // This MUST be the first argument. @@ -78,29 +81,37 @@ export class PowerShellProcess { powerShellArgs.push("-NoProfile"); // Only add ExecutionPolicy param on Windows - if (utils.isWindows && this.sessionSettings.developer.setExecutionPolicy) { + if ( + utils.isWindows && + this.sessionSettings.developer.setExecutionPolicy + ) { powerShellArgs.push("-ExecutionPolicy", "Bypass"); } - const startEditorServices = "Import-Module '" + + const startEditorServices = + "Import-Module '" + utils.escapeSingleQuotes(psesModulePath) + - "'; Start-EditorServices " + this.startPsesArgs; + "'; Start-EditorServices " + + this.startPsesArgs; // On Windows we unfortunately can't Base64 encode the startup command // because it annoys some poorly implemented anti-virus scanners. if (utils.isWindows) { - powerShellArgs.push( - "-Command", - startEditorServices); + powerShellArgs.push("-Command", startEditorServices); } else { // Otherwise use -EncodedCommand for better quote support. - this.logger.writeDebug("Using Base64 -EncodedCommand but logging as -Command equivalent."); + this.logger.writeDebug( + "Using Base64 -EncodedCommand but logging as -Command equivalent.", + ); powerShellArgs.push( "-EncodedCommand", - Buffer.from(startEditorServices, "utf16le").toString("base64")); + Buffer.from(startEditorServices, "utf16le").toString("base64"), + ); } - this.logger.writeDebug(`Starting process: ${this.exePath} ${powerShellArgs.slice(0, -2).join(" ")} -Command ${startEditorServices}`); + this.logger.writeDebug( + `Starting process: ${this.exePath} ${powerShellArgs.slice(0, -2).join(" ")} -Command ${startEditorServices}`, + ); // Make sure no old session file exists await this.deleteSessionFile(this.sessionFilePath); @@ -110,11 +121,13 @@ export class PowerShellProcess { let envMixin = {}; if (this.shellIntegrationEnabled) { envMixin = { - "VSCODE_INJECTION": "1", + VSCODE_INJECTION: "1", // There is no great way to check if we are running stable VS // Code. Since this is used to disable experimental features, we // default to stable unless we're definitely running Insiders. - "VSCODE_STABLE": vscode.env.appName.includes("Insiders") ? "0" : "1", + VSCODE_STABLE: vscode.env.appName.includes("Insiders") + ? "0" + : "1", // Maybe one day we can set VSCODE_NONCE... }; } @@ -122,34 +135,46 @@ export class PowerShellProcess { // Enables Hot Reload in .NET for the attached process // https://devblogs.microsoft.com/devops/net-enc-support-for-lambdas-and-other-improvements-in-visual-studio-2015/ if (this.devMode) { - (envMixin as Record).COMPLUS_FORCEENC = "1"; + (envMixin as Record).COMPLUS_FORCEENC = "1"; } // Launch PowerShell in the integrated terminal const terminalOptions: vscode.TerminalOptions = { - name: this.isTemp ? `${PowerShellProcess.title} (TEMP)` : PowerShellProcess.title, + name: this.isTemp + ? `${PowerShellProcess.title} (TEMP)` + : PowerShellProcess.title, shellPath: this.exePath, shellArgs: powerShellArgs, cwd: await validateCwdSetting(this.logger), env: envMixin, iconPath: new vscode.ThemeIcon("terminal-powershell"), isTransient: true, - hideFromUser: this.sessionSettings.integratedConsole.startInBackground, - location: vscode.TerminalLocation[this.sessionSettings.integratedConsole.startLocation], + hideFromUser: + this.sessionSettings.integratedConsole.startInBackground, + location: + vscode.TerminalLocation[ + this.sessionSettings.integratedConsole.startLocation + ], }; // Subscribe a log event for when the terminal closes (this fires for // all terminals and the event itself checks if it's our terminal). This // subscription should happen before we create the terminal so if it // fails immediately, the event fires. - this.consoleCloseSubscription = vscode.window.onDidCloseTerminal((terminal) => { this.onTerminalClose(terminal); }); + this.consoleCloseSubscription = vscode.window.onDidCloseTerminal( + (terminal) => { + this.onTerminalClose(terminal); + }, + ); this.consoleTerminal = vscode.window.createTerminal(terminalOptions); this.pid = await this.getPid(); this.logger.write(`PowerShell process started with PID: ${this.pid}`); this.pidUpdateEmitter?.fire(this.pid); - if (this.sessionSettings.integratedConsole.showOnStartup - && !this.sessionSettings.integratedConsole.startInBackground) { + if ( + this.sessionSettings.integratedConsole.showOnStartup && + !this.sessionSettings.integratedConsole.startInBackground + ) { // We still need to run this to set the active terminal to the extension terminal. this.consoleTerminal.show(true); } @@ -171,7 +196,12 @@ export class PowerShellProcess { // This function should only be used after a failure has occurred because it is slow! public async getVersionCli(): Promise { const exec = promisify(cp.execFile); - const { stdout } = await exec(this.exePath, ["-NoProfile", "-NoLogo", "-Command", "$PSVersionTable.PSVersion.ToString()"]); + const { stdout } = await exec(this.exePath, [ + "-NoProfile", + "-NoLogo", + "-Command", + "$PSVersionTable.PSVersion.ToString()", + ]); return stdout.trim(); } @@ -185,7 +215,9 @@ export class PowerShellProcess { } public dispose(): void { - this.logger.writeDebug(`Disposing PowerShell process with PID: ${this.pid}`); + this.logger.writeDebug( + `Disposing PowerShell process with PID: ${this.pid}`, + ); void this.deleteSessionFile(this.sessionFilePath); @@ -216,19 +248,30 @@ export class PowerShellProcess { // So we try to start PowerShell with -Login // If it exits successfully, we return true // If it exits unsuccessfully, node throws, we catch, and return false - cp.execFileSync(pwshPath, ["-Login", "-NoProfile", "-NoLogo", "-Command", "exit 0"]); + cp.execFileSync(pwshPath, [ + "-Login", + "-NoProfile", + "-NoLogo", + "-Command", + "exit 0", + ]); } catch { return false; } return true; } - private async readSessionFile(sessionFilePath: vscode.Uri): Promise { - const fileContents = await vscode.workspace.fs.readFile(sessionFilePath); + private async readSessionFile( + sessionFilePath: vscode.Uri, + ): Promise { + const fileContents = + await vscode.workspace.fs.readFile(sessionFilePath); return JSON.parse(fileContents.toString()); } - private async deleteSessionFile(sessionFilePath: vscode.Uri): Promise { + private async deleteSessionFile( + sessionFilePath: vscode.Uri, + ): Promise { try { await vscode.workspace.fs.delete(sessionFilePath); } catch { @@ -236,15 +279,22 @@ export class PowerShellProcess { } } - private async waitForSessionFile(cancellationToken: vscode.CancellationToken): Promise { - const numOfTries = this.sessionSettings.developer.waitForSessionFileTimeoutSeconds; + private async waitForSessionFile( + cancellationToken: vscode.CancellationToken, + ): Promise { + const numOfTries = + this.sessionSettings.developer.waitForSessionFileTimeoutSeconds; const warnAt = numOfTries - PowerShellProcess.warnUserThreshold; // Check every second. - this.logger.writeDebug(`Waiting for session file: ${this.sessionFilePath}`); + this.logger.writeDebug( + `Waiting for session file: ${this.sessionFilePath}`, + ); for (let i = numOfTries; i > 0; i--) { if (cancellationToken.isCancellationRequested) { - this.logger.writeWarning("Canceled while waiting for session file."); + this.logger.writeWarning( + "Canceled while waiting for session file.", + ); return undefined; } @@ -259,7 +309,9 @@ export class PowerShellProcess { } if (warnAt === i) { - void this.logger.writeAndShowWarning("Loading the PowerShell extension is taking longer than expected. If you're using privilege enforcement software, this can affect start up performance."); + void this.logger.writeAndShowWarning( + "Loading the PowerShell extension is taking longer than expected. If you're using privilege enforcement software, this can affect start up performance.", + ); } // Wait a bit and try again. @@ -275,7 +327,9 @@ export class PowerShellProcess { return; } - this.logger.writeWarning(`PowerShell process terminated or Extension Terminal was closed, PID: ${this.pid}`); + this.logger.writeWarning( + `PowerShell process terminated or Extension Terminal was closed, PID: ${this.pid}`, + ); this.dispose(); } } diff --git a/src/session.ts b/src/session.ts index 1928d26ded..8ae1624bfb 100644 --- a/src/session.ts +++ b/src/session.ts @@ -5,51 +5,51 @@ import net = require("net"); import path = require("path"); import vscode = require("vscode"); import TelemetryReporter, { - type TelemetryEventProperties, - type TelemetryEventMeasurements, + type TelemetryEventMeasurements, + type TelemetryEventProperties, } from "@vscode/extension-telemetry"; import { Message } from "vscode-jsonrpc"; import { - type ILogger, - LanguageClientOutputChannelAdapter, - LspTraceParser, - PsesParser, + type ILogger, + LanguageClientOutputChannelAdapter, + LspTraceParser, + PsesParser, } from "./logging"; import { PowerShellProcess } from "./process"; import { - Settings, - changeSetting, - getSettings, - getEffectiveConfigurationTarget, - validateCwdSetting, + Settings, + changeSetting, + getEffectiveConfigurationTarget, + getSettings, + validateCwdSetting, } from "./settings"; import utils = require("./utils"); import { - CloseAction, - type CloseHandlerResult, - DocumentSelector, - ErrorAction, - type ErrorHandlerResult, - type LanguageClientOptions, - type Middleware, - NotificationType, - RequestType0, - type ResolveCodeLensSignature, - RevealOutputChannelOn, + CloseAction, + type CloseHandlerResult, + DocumentSelector, + ErrorAction, + type ErrorHandlerResult, + type LanguageClientOptions, + type Middleware, + NotificationType, + RequestType0, + type ResolveCodeLensSignature, + RevealOutputChannelOn, } from "vscode-languageclient"; import { LanguageClient, type StreamInfo } from "vscode-languageclient/node"; +import { SemVer, satisfies } from "semver"; import { UpdatePowerShell } from "./features/UpdatePowerShell"; +import { LanguageClientConsumer } from "./languageClientConsumer"; import { - getPlatformDetails, - type IPlatformDetails, - type IPowerShellExeDetails, - OperatingSystem, - PowerShellExeFinder, + type IPlatformDetails, + type IPowerShellExeDetails, + OperatingSystem, + PowerShellExeFinder, + getPlatformDetails, } from "./platform"; -import { LanguageClientConsumer } from "./languageClientConsumer"; -import { SemVer, satisfies } from "semver"; enum SessionStatus { NotStarted = "Not Started", @@ -83,17 +83,21 @@ export interface IPowerShellVersionDetails { architecture: string; } -export type IReadSessionFileCallback = (details: IEditorServicesSessionDetails) => void; +export type IReadSessionFileCallback = ( + details: IEditorServicesSessionDetails, +) => void; -export const SendKeyPressNotificationType = - new NotificationType("powerShell/sendKeyPress"); +export const SendKeyPressNotificationType = new NotificationType( + "powerShell/sendKeyPress", +); export const ExecutionBusyStatusNotificationType = new NotificationType("powerShell/executionBusyStatus"); -export const PowerShellVersionRequestType = - new RequestType0( - "powerShell/getVersion"); +export const PowerShellVersionRequestType = new RequestType0< + IPowerShellVersionDetails, + void +>("powerShell/getVersion"); export class SessionManager implements Middleware { public HostName: string; @@ -115,7 +119,9 @@ export class SessionManager implements Middleware { private sessionsFolder: vscode.Uri; private sessionStatus: SessionStatus = SessionStatus.NotStarted; private shellIntegrationEnabled = false; - private startCancellationTokenSource: vscode.CancellationTokenSource | undefined; + private startCancellationTokenSource: + | vscode.CancellationTokenSource + | undefined; private suppressRestartPrompt = false; private versionDetails: IPowerShellVersionDetails | undefined; private traceLogLevelHandler?: vscode.Disposable; @@ -129,12 +135,16 @@ export class SessionManager implements Middleware { displayName: string, hostVersion: string, publisher: string, - private telemetryReporter: TelemetryReporter) { + private telemetryReporter: TelemetryReporter, + ) { // Create the language status item this.languageStatusItem = this.createStatusBarItem(); // We have to override the scheme because it defaults to // 'vscode-userdata' which breaks UNC paths. - this.sessionsFolder = vscode.Uri.joinPath(extensionContext.globalStorageUri.with({ scheme: "file" }), "sessions"); + this.sessionsFolder = vscode.Uri.joinPath( + extensionContext.globalStorageUri.with({ scheme: "file" }), + "sessions", + ); this.platformDetails = getPlatformDetails(); this.HostName = hostName; @@ -143,12 +153,15 @@ export class SessionManager implements Middleware { this.Publisher = publisher; const osBitness = this.platformDetails.isOS64Bit ? "64-bit" : "32-bit"; - const procBitness = this.platformDetails.isProcess64Bit ? "64-bit" : "32-bit"; + const procBitness = this.platformDetails.isProcess64Bit + ? "64-bit" + : "32-bit"; this.logger.write( - `Visual Studio Code: v${vscode.version} ${procBitness}` - + ` on ${OperatingSystem[this.platformDetails.operatingSystem]} ${osBitness}`, - `${this.DisplayName} Extension: v${this.HostVersion}`); + `Visual Studio Code: v${vscode.version} ${procBitness}` + + ` on ${OperatingSystem[this.platformDetails.operatingSystem]} ${osBitness}`, + `${this.DisplayName} Extension: v${this.HostVersion}`, + ); // Fix the host version so that PowerShell can consume it. // This is needed when the extension uses a prerelease @@ -176,38 +189,41 @@ export class SessionManager implements Middleware { // We've made this function idempotent, so it can used to ensure the session has started. public async start(): Promise { switch (this.sessionStatus) { - case SessionStatus.NotStarted: - // Go ahead and start. - break; - case SessionStatus.Starting: - // A simple lock because this function isn't re-entrant. - this.logger.writeWarning("Re-entered 'start' so waiting..."); - await this.waitWhileStarting(); - return; - case SessionStatus.Running: - // We're started, just return. - this.logger.writeDebug("Already started."); - return; - case SessionStatus.Busy: - // We're started but busy so notify and return. - // TODO: Make a proper notification for this and when IntelliSense is blocked. - this.logger.write("The Extension Terminal is currently busy, please wait for your task to finish!"); - return; - case SessionStatus.Stopping: - // Wait until done stopping, then start. - this.logger.writeDebug("Still stopping."); - await this.waitWhileStopping(); - break; - case SessionStatus.Failed: - // Try to start again. - this.logger.writeDebug("Previously failed, starting again."); - break; + case SessionStatus.NotStarted: + // Go ahead and start. + break; + case SessionStatus.Starting: + // A simple lock because this function isn't re-entrant. + this.logger.writeWarning("Re-entered 'start' so waiting..."); + await this.waitWhileStarting(); + return; + case SessionStatus.Running: + // We're started, just return. + this.logger.writeDebug("Already started."); + return; + case SessionStatus.Busy: + // We're started but busy so notify and return. + // TODO: Make a proper notification for this and when IntelliSense is blocked. + this.logger.write( + "The Extension Terminal is currently busy, please wait for your task to finish!", + ); + return; + case SessionStatus.Stopping: + // Wait until done stopping, then start. + this.logger.writeDebug("Still stopping."); + await this.waitWhileStopping(); + break; + case SessionStatus.Failed: + // Try to start again. + this.logger.writeDebug("Previously failed, starting again."); + break; } // This status needs to be set immediately so the above check works this.setSessionStatus("Starting...", SessionStatus.Starting); - this.startCancellationTokenSource = new vscode.CancellationTokenSource(); + this.startCancellationTokenSource = + new vscode.CancellationTokenSource(); const cancellationToken = this.startCancellationTokenSource.token; // Create a folder for the session files. @@ -217,31 +233,41 @@ export class SessionManager implements Middleware { await this.migrateWhitespaceAroundPipeSetting(); // Update non-PowerShell settings. - this.shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get("enabled") ?? false; + this.shellIntegrationEnabled = + vscode.workspace + .getConfiguration("terminal.integrated.shellIntegration") + .get("enabled") ?? false; // Find the PowerShell executable to use for the server. this.PowerShellExeDetails = await this.findPowerShell(); if (this.PowerShellExeDetails === undefined) { - const message = "Unable to find PowerShell!" - + " Do you have it installed?" - + " You can also configure custom installations" - + " with the 'powershell.powerShellAdditionalExePaths' setting."; + const message = + "Unable to find PowerShell!" + + " Do you have it installed?" + + " You can also configure custom installations" + + " with the 'powershell.powerShellAdditionalExePaths' setting."; void this.setSessionFailedGetPowerShell(message); return; } // Refresh the status with the found executable details. this.refreshSessionStatus(); - this.logger.write(`Starting '${this.PowerShellExeDetails.displayName}' at: ${this.PowerShellExeDetails.exePath}`); + this.logger.write( + `Starting '${this.PowerShellExeDetails.displayName}' at: ${this.PowerShellExeDetails.exePath}`, + ); // Start the server. this.languageServerProcess = await this.startLanguageServerProcess( this.PowerShellExeDetails, - cancellationToken); + cancellationToken, + ); // Check that we got session details and that they had a "started" status. - if (this.sessionDetails === undefined || !this.sessionStarted(this.sessionDetails)) { + if ( + this.sessionDetails === undefined || + !this.sessionStarted(this.sessionDetails) + ) { if (!cancellationToken.isCancellationRequested) { // If it failed but we didn't cancel it, handle the common reasons. await this.handleFailedProcess(this.languageServerProcess); @@ -252,22 +278,32 @@ export class SessionManager implements Middleware { } // If we got good session details from the server, try to connect to it. - this.languageClient = await this.startLanguageClient(this.sessionDetails); + this.languageClient = await this.startLanguageClient( + this.sessionDetails, + ); if (this.languageClient.isRunning()) { this.versionDetails = await this.getVersionDetails(); if (this.versionDetails === undefined) { - void this.setSessionFailedOpenBug("Unable to get version details!"); + void this.setSessionFailedOpenBug( + "Unable to get version details!", + ); return; } - this.logger.write(`Started PowerShell v${this.versionDetails.version}.`); + this.logger.write( + `Started PowerShell v${this.versionDetails.version}.`, + ); this.setSessionRunningStatus(); // Yay, we made it! await this.writePidIfInDevMode(this.languageServerProcess); // Fire and forget the updater. - const updater = new UpdatePowerShell(this.sessionSettings, this.logger, this.versionDetails); + const updater = new UpdatePowerShell( + this.sessionSettings, + this.logger, + this.versionDetails, + ); void updater.checkForUpdate(); } else { void this.setSessionFailedOpenBug("Never finished startup!"); @@ -286,7 +322,9 @@ export class SessionManager implements Middleware { await this.languageClient?.stop(3000); await this.languageClient?.dispose(); } catch (err) { - this.logger.writeError(`Error occurred while stopping language client:\n${err}`); + this.logger.writeError( + `Error occurred while stopping language client:\n${err}`, + ); } this.languageClient = undefined; @@ -320,7 +358,9 @@ export class SessionManager implements Middleware { if (exeNameOverride) { // Reset the version and PowerShell details since we're launching a // new executable. - this.logger.writeDebug(`Starting with executable overriden to: ${exeNameOverride}`); + this.logger.writeDebug( + `Starting with executable overriden to: ${exeNameOverride}`, + ); this.sessionSettings.powerShellDefaultVersion = exeNameOverride; this.versionDetails = undefined; this.PowerShellExeDetails = undefined; @@ -330,19 +370,31 @@ export class SessionManager implements Middleware { } /** In Development mode, write the PID to a file where the parent session can find it, to attach the dotnet debugger. */ - private async writePidIfInDevMode(pwshProcess: PowerShellProcess): Promise { - if (this.extensionContext.extensionMode !== vscode.ExtensionMode.Development) { return; } + private async writePidIfInDevMode( + pwshProcess: PowerShellProcess, + ): Promise { + if ( + this.extensionContext.extensionMode !== + vscode.ExtensionMode.Development + ) { + return; + } const parentSessionId = process.env.VSCODE_PARENT_SESSION_ID; - const pidFilePath = vscode.Uri.joinPath(this.sessionsFolder, `PSES-${parentSessionId}.pid`); + const pidFilePath = vscode.Uri.joinPath( + this.sessionsFolder, + `PSES-${parentSessionId}.pid`, + ); - if (parentSessionId === undefined) { return; } + if (parentSessionId === undefined) { + return; + } const fs = vscode.workspace.fs; const pid = (await pwshProcess.getPid())!.toString(); await fs.writeFile(pidFilePath, Buffer.from(pid)); const deletePidOnExit = pwshProcess.onExited(() => { deletePidOnExit.dispose(); - fs.delete(pidFilePath, {useTrash: false}); + fs.delete(pidFilePath, { useTrash: false }); console.log(`Deleted PID file: ${pidFilePath}`); }); this.registeredCommands.push(deletePidOnExit); @@ -352,32 +404,45 @@ export class SessionManager implements Middleware { public getSessionDetails(): IEditorServicesSessionDetails | undefined { // This is used by the debugger which should have already called `start`. if (this.sessionDetails === undefined) { - void this.logger.writeAndShowError("PowerShell session unavailable for debugging!"); + void this.logger.writeAndShowError( + "PowerShell session unavailable for debugging!", + ); } return this.sessionDetails; } public async getLanguageServerPid(): Promise { if (this.languageServerProcess === undefined) { - void this.logger.writeAndShowError("PowerShell Extension Terminal unavailable!"); + void this.logger.writeAndShowError( + "PowerShell Extension Terminal unavailable!", + ); } return this.languageServerProcess?.getPid(); } - public getPowerShellVersionDetails(): IPowerShellVersionDetails | undefined { + public getPowerShellVersionDetails(): + | IPowerShellVersionDetails + | undefined { return this.versionDetails; } private getNewSessionFilePath(): vscode.Uri { const uniqueId: number = Math.floor(100000 + Math.random() * 900000); - return vscode.Uri.joinPath(this.sessionsFolder, `PSES-VSCode-${process.env.VSCODE_PID}-${uniqueId}.json`); + return vscode.Uri.joinPath( + this.sessionsFolder, + `PSES-VSCode-${process.env.VSCODE_PID}-${uniqueId}.json`, + ); } - public setLanguageClientConsumers(languageClientConsumers: LanguageClientConsumer[]): void { + public setLanguageClientConsumers( + languageClientConsumers: LanguageClientConsumer[], + ): void { this.languageClientConsumers = languageClientConsumers; } - public async createDebugSessionProcess(settings: Settings): Promise { + public async createDebugSessionProcess( + settings: Settings, + ): Promise { // NOTE: We only support one temporary Extension Terminal at a time. To // support more, we need to track each separately, and tie the session // for the event handler to the right process (and dispose of the event @@ -385,7 +450,9 @@ export class SessionManager implements Middleware { this.debugSessionProcess?.dispose(); this.debugEventHandler?.dispose(); if (this.PowerShellExeDetails === undefined) { - return Promise.reject(new Error("Required PowerShellExeDetails undefined!")); + return Promise.reject( + new Error("Required PowerShellExeDetails undefined!"), + ); } // TODO: It might not be totally necessary to update the session @@ -394,28 +461,30 @@ export class SessionManager implements Middleware { this.sessionSettings = settings; const bundledModulesPath = await this.getBundledModulesPath(); - this.debugSessionProcess = - new PowerShellProcess( - this.PowerShellExeDetails.exePath, + this.debugSessionProcess = new PowerShellProcess( + this.PowerShellExeDetails.exePath, + bundledModulesPath, + true, + false, + this.logger, + this.extensionContext.logUri, + this.getEditorServicesArgs( bundledModulesPath, - true, - false, - this.logger, - this.extensionContext.logUri, - this.getEditorServicesArgs(bundledModulesPath, this.PowerShellExeDetails) + "-DebugServiceOnly ", - this.getNewSessionFilePath(), - this.sessionSettings); + this.PowerShellExeDetails, + ) + "-DebugServiceOnly ", + this.getNewSessionFilePath(), + this.sessionSettings, + ); // Similar to the regular Extension Terminal, we need to send a key // press to the process spawned for temporary Extension Terminals when // the server requests a cancellation os Console.ReadKey. - this.debugEventHandler = vscode.debug.onDidReceiveDebugSessionCustomEvent( - e => { + this.debugEventHandler = + vscode.debug.onDidReceiveDebugSessionCustomEvent((e) => { if (e.event === "powerShell/sendKeyPress") { this.debugSessionProcess?.sendKeyPress(); } - } - ); + }); return this.debugSessionProcess; } @@ -430,31 +499,39 @@ export class SessionManager implements Middleware { public resolveCodeLens( codeLens: vscode.CodeLens, token: vscode.CancellationToken, - next: ResolveCodeLensSignature): vscode.ProviderResult { + next: ResolveCodeLensSignature, + ): vscode.ProviderResult { const resolvedCodeLens = next(codeLens, token); - const resolveFunc = - (codeLensToFix: vscode.CodeLens): vscode.CodeLens => { - if (codeLensToFix.command?.command === "editor.action.showReferences") { - const oldArgs = codeLensToFix.command.arguments; - if (oldArgs === undefined || oldArgs.length < 3) { - this.logger.writeError("Code Lens arguments were malformed!"); - return codeLensToFix; - } - - // Our JSON objects don't get handled correctly by - // VS Code's built in editor.action.showReferences - // command so we need to convert them into the - // appropriate types to send them as command - // arguments. + const resolveFunc = ( + codeLensToFix: vscode.CodeLens, + ): vscode.CodeLens => { + if ( + codeLensToFix.command?.command === + "editor.action.showReferences" + ) { + const oldArgs = codeLensToFix.command.arguments; + if (oldArgs === undefined || oldArgs.length < 3) { + this.logger.writeError( + "Code Lens arguments were malformed!", + ); + return codeLensToFix; + } - codeLensToFix.command.arguments = [ - vscode.Uri.parse(oldArgs[0]), - new vscode.Position(oldArgs[1].line, oldArgs[1].character), - oldArgs[2].map((position: { + // Our JSON objects don't get handled correctly by + // VS Code's built in editor.action.showReferences + // command so we need to convert them into the + // appropriate types to send them as command + // arguments. + + codeLensToFix.command.arguments = [ + vscode.Uri.parse(oldArgs[0]), + new vscode.Position(oldArgs[1].line, oldArgs[1].character), + oldArgs[2].map( + (position: { uri: string; range: { - start: { line: number; character: number; }; - end: { line: number; character: number; }; + start: { line: number; character: number }; + end: { line: number; character: number }; }; }) => { return new vscode.Location( @@ -463,18 +540,23 @@ export class SessionManager implements Middleware { position.range.start.line, position.range.start.character, position.range.end.line, - position.range.end.character)); - }), - ]; - } + position.range.end.character, + ), + ); + }, + ), + ]; + } - return codeLensToFix; - }; + return codeLensToFix; + }; // TODO: This makes zero sense, but appears to be "working" and copied by others per https://github.com/microsoft/vscode-languageserver-node/issues/495. Thing is, ESLint says these conditionals are always truthy. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if ((resolvedCodeLens as Thenable).then) { - return (resolvedCodeLens as Thenable).then(resolveFunc); + return (resolvedCodeLens as Thenable).then( + resolveFunc, + ); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (resolvedCodeLens as vscode.CodeLens) { return resolveFunc(resolvedCodeLens as vscode.CodeLens); @@ -487,36 +569,75 @@ export class SessionManager implements Middleware { // codeFormatting.whitespaceAroundPipe to new setting // codeFormatting.addWhitespaceAroundPipe. private async migrateWhitespaceAroundPipeSetting(): Promise { - const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId); + const configuration = vscode.workspace.getConfiguration( + utils.PowerShellLanguageId, + ); const deprecatedSetting = "codeFormatting.whitespaceAroundPipe"; const newSetting = "codeFormatting.addWhitespaceAroundPipe"; - const configurationTargetOfNewSetting = getEffectiveConfigurationTarget(newSetting); - const configurationTargetOfOldSetting = getEffectiveConfigurationTarget(deprecatedSetting); - if (configurationTargetOfOldSetting !== undefined && configurationTargetOfNewSetting === undefined) { - this.logger.writeWarning("Deprecated setting: whitespaceAroundPipe"); - const value = configuration.get(deprecatedSetting, configurationTargetOfOldSetting); - await changeSetting(newSetting, value, configurationTargetOfOldSetting, this.logger); - await changeSetting(deprecatedSetting, undefined, configurationTargetOfOldSetting, this.logger); + const configurationTargetOfNewSetting = + getEffectiveConfigurationTarget(newSetting); + const configurationTargetOfOldSetting = + getEffectiveConfigurationTarget(deprecatedSetting); + if ( + configurationTargetOfOldSetting !== undefined && + configurationTargetOfNewSetting === undefined + ) { + this.logger.writeWarning( + "Deprecated setting: whitespaceAroundPipe", + ); + const value = configuration.get( + deprecatedSetting, + configurationTargetOfOldSetting, + ); + await changeSetting( + newSetting, + value, + configurationTargetOfOldSetting, + this.logger, + ); + await changeSetting( + deprecatedSetting, + undefined, + configurationTargetOfOldSetting, + this.logger, + ); } } /** There are some changes we cannot "hot" set, so these require a restart of the session */ - private async restartOnCriticalConfigChange(changeEvent: vscode.ConfigurationChangeEvent): Promise { - if (this.suppressRestartPrompt) {return;} - if (this.sessionStatus !== SessionStatus.Running) {return;} + private async restartOnCriticalConfigChange( + changeEvent: vscode.ConfigurationChangeEvent, + ): Promise { + if (this.suppressRestartPrompt) { + return; + } + if (this.sessionStatus !== SessionStatus.Running) { + return; + } // Restart not needed if shell integration is enabled but the shell is backgrounded. const settings = getSettings(); - if (changeEvent.affectsConfiguration("terminal.integrated.shellIntegration.enabled")) { - const shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get("enabled") ?? false; - if (shellIntegrationEnabled && !settings.integratedConsole.startInBackground) { + if ( + changeEvent.affectsConfiguration( + "terminal.integrated.shellIntegration.enabled", + ) + ) { + const shellIntegrationEnabled = + vscode.workspace + .getConfiguration("terminal.integrated.shellIntegration") + .get("enabled") ?? false; + if ( + shellIntegrationEnabled && + !settings.integratedConsole.startInBackground + ) { return this.restartWithPrompt(); } } // Early return if the change doesn't affect the PowerShell extension settings from this point forward - if (!changeEvent.affectsConfiguration("powershell")) {return;} - + if (!changeEvent.affectsConfiguration("powershell")) { + return; + } // Detect any setting changes that would affect the session. const coldRestartSettingNames = [ @@ -525,20 +646,32 @@ export class SessionManager implements Middleware { "developer.editorServicesLogLevel", ]; for (const settingName of coldRestartSettingNames) { - if (changeEvent.affectsConfiguration("powershell" + "." + settingName)) { + if ( + changeEvent.affectsConfiguration( + "powershell" + "." + settingName, + ) + ) { return this.restartWithPrompt(); } } // TODO: Migrate these to affectsConfiguration style above - if (settings.cwd !== this.sessionSettings.cwd - || settings.powerShellDefaultVersion !== this.sessionSettings.powerShellDefaultVersion - || settings.developer.bundledModulesPath !== this.sessionSettings.developer.bundledModulesPath - || settings.developer.editorServicesWaitForDebugger !== this.sessionSettings.developer.editorServicesWaitForDebugger - || settings.developer.setExecutionPolicy !== this.sessionSettings.developer.setExecutionPolicy - || settings.integratedConsole.useLegacyReadLine !== this.sessionSettings.integratedConsole.useLegacyReadLine - || settings.integratedConsole.startInBackground !== this.sessionSettings.integratedConsole.startInBackground - || settings.integratedConsole.startLocation !== this.sessionSettings.integratedConsole.startLocation + if ( + settings.cwd !== this.sessionSettings.cwd || + settings.powerShellDefaultVersion !== + this.sessionSettings.powerShellDefaultVersion || + settings.developer.bundledModulesPath !== + this.sessionSettings.developer.bundledModulesPath || + settings.developer.editorServicesWaitForDebugger !== + this.sessionSettings.developer.editorServicesWaitForDebugger || + settings.developer.setExecutionPolicy !== + this.sessionSettings.developer.setExecutionPolicy || + settings.integratedConsole.useLegacyReadLine !== + this.sessionSettings.integratedConsole.useLegacyReadLine || + settings.integratedConsole.startInBackground !== + this.sessionSettings.integratedConsole.startInBackground || + settings.integratedConsole.startLocation !== + this.sessionSettings.integratedConsole.startLocation ) { return this.restartWithPrompt(); } @@ -548,7 +681,9 @@ export class SessionManager implements Middleware { this.logger.writeDebug("Settings changed, prompting to restart..."); const response = await vscode.window.showInformationMessage( "The PowerShell runtime configuration has changed, would you like to start a new session?", - "Yes", "No"); + "Yes", + "No", + ); if (response === "Yes") { await this.restartSession(); @@ -557,11 +692,27 @@ export class SessionManager implements Middleware { private registerCommands(): void { this.registeredCommands = [ - vscode.commands.registerCommand("PowerShell.RestartSession", async () => { await this.restartSession(); }), - vscode.commands.registerCommand(this.ShowSessionMenuCommandName, async () => { await this.showSessionMenu(); }), - vscode.workspace.onDidChangeConfiguration((e) => this.restartOnCriticalConfigChange(e)), vscode.commands.registerCommand( - "PowerShell.ShowSessionConsole", (isExecute?: boolean) => { this.showSessionTerminal(isExecute); }) + "PowerShell.RestartSession", + async () => { + await this.restartSession(); + }, + ), + vscode.commands.registerCommand( + this.ShowSessionMenuCommandName, + async () => { + await this.showSessionMenu(); + }, + ), + vscode.workspace.onDidChangeConfiguration((e) => + this.restartOnCriticalConfigChange(e), + ), + vscode.commands.registerCommand( + "PowerShell.ShowSessionConsole", + (isExecute?: boolean) => { + this.showSessionTerminal(isExecute); + }, + ), ]; } @@ -570,7 +721,8 @@ export class SessionManager implements Middleware { const powershellExeFinder = new PowerShellExeFinder( this.platformDetails, this.sessionSettings.powerShellAdditionalExePaths, - this.logger); + this.logger, + ); let foundPowerShell: IPowerShellExeDetails | undefined; try { @@ -579,19 +731,35 @@ export class SessionManager implements Middleware { if (wantedName !== "") { for await (const details of powershellExeFinder.enumeratePowerShellInstallations()) { // Need to compare names case-insensitively, from https://stackoverflow.com/a/2140723 - if (wantedName.localeCompare(details.displayName, undefined, { sensitivity: "accent" }) === 0) { + if ( + wantedName.localeCompare( + details.displayName, + undefined, + { sensitivity: "accent" }, + ) === 0 + ) { defaultPowerShell = details; break; } } } - foundPowerShell = defaultPowerShell ?? await powershellExeFinder.getFirstAvailablePowerShellInstallation(); - if (wantedName !== "" && defaultPowerShell === undefined && foundPowerShell !== undefined) { - void this.logger.writeAndShowWarning(`The 'powerShellDefaultVersion' setting was '${wantedName}' but this was not found!` - + ` Instead using first available installation '${foundPowerShell.displayName}' at '${foundPowerShell.exePath}'!`); + foundPowerShell = + defaultPowerShell ?? + (await powershellExeFinder.getFirstAvailablePowerShellInstallation()); + if ( + wantedName !== "" && + defaultPowerShell === undefined && + foundPowerShell !== undefined + ) { + void this.logger.writeAndShowWarning( + `The 'powerShellDefaultVersion' setting was '${wantedName}' but this was not found!` + + ` Instead using first available installation '${foundPowerShell.displayName}' at '${foundPowerShell.exePath}'!`, + ); } } catch (err) { - this.logger.writeError(`Error occurred while searching for a PowerShell executable:\n${err}`); + this.logger.writeError( + `Error occurred while searching for a PowerShell executable:\n${err}`, + ); } return foundPowerShell; @@ -599,104 +767,140 @@ export class SessionManager implements Middleware { private async startLanguageServerProcess( powerShellExeDetails: IPowerShellExeDetails, - cancellationToken: vscode.CancellationToken): Promise { - + cancellationToken: vscode.CancellationToken, + ): Promise { const bundledModulesPath = await this.getBundledModulesPath(); // Dispose any stale terminals from previous killed sessions. PowerShellProcess.cleanUpTerminals(); - const languageServerProcess = - new PowerShellProcess( - powerShellExeDetails.exePath, + const languageServerProcess = new PowerShellProcess( + powerShellExeDetails.exePath, + bundledModulesPath, + false, + this.shellIntegrationEnabled, + this.logger, + this.extensionContext.logUri, + this.getEditorServicesArgs( bundledModulesPath, - false, - this.shellIntegrationEnabled, - this.logger, - this.extensionContext.logUri, - this.getEditorServicesArgs(bundledModulesPath, powerShellExeDetails), - this.getNewSessionFilePath(), - this.sessionSettings, - this.extensionContext.extensionMode == vscode.ExtensionMode.Development); + powerShellExeDetails, + ), + this.getNewSessionFilePath(), + this.sessionSettings, + this.extensionContext.extensionMode == + vscode.ExtensionMode.Development, + ); - languageServerProcess.onExited( - () => { - LanguageClientConsumer.onLanguageClientExited(); + languageServerProcess.onExited(() => { + LanguageClientConsumer.onLanguageClientExited(); - if (this.sessionStatus === SessionStatus.Running - || this.sessionStatus === SessionStatus.Busy) { - this.setSessionStatus("Session Exited!", SessionStatus.Failed); - void this.promptForRestart(); - } - }); + if ( + this.sessionStatus === SessionStatus.Running || + this.sessionStatus === SessionStatus.Busy + ) { + this.setSessionStatus("Session Exited!", SessionStatus.Failed); + void this.promptForRestart(); + } + }); - this.sessionDetails = await languageServerProcess.start(cancellationToken); + this.sessionDetails = + await languageServerProcess.start(cancellationToken); return languageServerProcess; } // The process failed to start, so check for common user errors (generally // out-of-support versions of PowerShell). - private async handleFailedProcess(powerShellProcess: PowerShellProcess): Promise { + private async handleFailedProcess( + powerShellProcess: PowerShellProcess, + ): Promise { const version = await powerShellProcess.getVersionCli(); let shouldUpdate = true; if (satisfies(version, "<5.1.0")) { - void this.setSessionFailedGetPowerShell(`PowerShell v${version} is not supported, please update!`); + void this.setSessionFailedGetPowerShell( + `PowerShell v${version} is not supported, please update!`, + ); } else if (satisfies(version, ">=5.1.0 <6.0.0")) { - void this.setSessionFailedGetPowerShell("It looks like you're trying to use Windows PowerShell, which is supported on a best-effort basis. Can you try PowerShell 7?"); + void this.setSessionFailedGetPowerShell( + "It looks like you're trying to use Windows PowerShell, which is supported on a best-effort basis. Can you try PowerShell 7?", + ); } else if (satisfies(version, ">=6.0.0 <7.4.0")) { - void this.setSessionFailedGetPowerShell(`PowerShell v${version} has reached end-of-support, please update!`); + void this.setSessionFailedGetPowerShell( + `PowerShell v${version} has reached end-of-support, please update!`, + ); } else { shouldUpdate = false; - void this.setSessionFailedOpenBug("PowerShell Language Server process didn't start!"); + void this.setSessionFailedOpenBug( + "PowerShell Language Server process didn't start!", + ); } if (shouldUpdate) { // Run the update notifier since it won't run later as we failed // to start, but we have enough details to do so now. const versionDetails: IPowerShellVersionDetails = { - "version": version, - "edition": "", // Unused by UpdatePowerShell - "commit": version, // Actually used by UpdatePowerShell - "architecture": process.arch // Best guess based off Code's architecture + version: version, + edition: "", // Unused by UpdatePowerShell + commit: version, // Actually used by UpdatePowerShell + architecture: process.arch, // Best guess based off Code's architecture }; - const updater = new UpdatePowerShell(this.sessionSettings, this.logger, versionDetails); + const updater = new UpdatePowerShell( + this.sessionSettings, + this.logger, + versionDetails, + ); void updater.checkForUpdate(); } } - private sessionStarted(sessionDetails: IEditorServicesSessionDetails): boolean { - this.logger.writeDebug(`Session details: ${JSON.stringify(sessionDetails, undefined, 2)}`); - if (sessionDetails.status === "started") { // Successful server start with a session file + private sessionStarted( + sessionDetails: IEditorServicesSessionDetails, + ): boolean { + this.logger.writeDebug( + `Session details: ${JSON.stringify(sessionDetails, undefined, 2)}`, + ); + if (sessionDetails.status === "started") { + // Successful server start with a session file return true; } - if (sessionDetails.status === "failed") { // Server started but indicated it failed + if (sessionDetails.status === "failed") { + // Server started but indicated it failed if (sessionDetails.reason === "powerShellVersion") { - void this.setSessionFailedGetPowerShell(`PowerShell ${sessionDetails.powerShellVersion} is not supported, please update!`); - } else if (sessionDetails.reason === "dotNetVersion") { // Only applies to PowerShell 5.1 - void this.setSessionFailedGetDotNet(".NET Framework is out-of-date, please install at least 4.8!"); + void this.setSessionFailedGetPowerShell( + `PowerShell ${sessionDetails.powerShellVersion} is not supported, please update!`, + ); + } else if (sessionDetails.reason === "dotNetVersion") { + // Only applies to PowerShell 5.1 + void this.setSessionFailedGetDotNet( + ".NET Framework is out-of-date, please install at least 4.8!", + ); } else { - void this.setSessionFailedOpenBug(`PowerShell could not be started for an unknown reason: ${sessionDetails.reason}`); + void this.setSessionFailedOpenBug( + `PowerShell could not be started for an unknown reason: ${sessionDetails.reason}`, + ); } } else { - void this.setSessionFailedOpenBug(`PowerShell could not be started with an unknown status: ${sessionDetails.status}, and reason: ${sessionDetails.reason}`); + void this.setSessionFailedOpenBug( + `PowerShell could not be started with an unknown status: ${sessionDetails.status}, and reason: ${sessionDetails.reason}`, + ); } return false; } - private async startLanguageClient(sessionDetails: IEditorServicesSessionDetails): Promise { + private async startLanguageClient( + sessionDetails: IEditorServicesSessionDetails, + ): Promise { this.logger.writeDebug("Connecting to language service..."); const connectFunc = (): Promise => { - return new Promise( - (resolve, _reject) => { - const socket = net.connect(sessionDetails.languageServicePipeName); - socket.on( - "connect", - () => { - this.logger.writeDebug("Language service connected."); - resolve({ writer: socket, reader: socket }); - }); + return new Promise((resolve, _reject) => { + const socket = net.connect( + sessionDetails.languageServicePipeName, + ); + socket.on("connect", () => { + this.logger.writeDebug("Language service connected."); + resolve({ writer: socket, reader: socket }); }); + }); }; const clientOptions: LanguageClientOptions = { @@ -704,7 +908,11 @@ export class SessionManager implements Middleware { synchronize: { // TODO: This is deprecated and they should be pulled by the server. // backend uses "files" and "search" to ignore references. - configurationSection: [utils.PowerShellLanguageId, "files", "search"], + configurationSection: [ + utils.PowerShellLanguageId, + "files", + "search", + ], // TODO: fileEvents: vscode.workspace.createFileSystemWatcher('**/.eslintrc') }, // NOTE: Some settings are only applicable on startup, so we send them during initialization. @@ -714,15 +922,22 @@ export class SessionManager implements Middleware { enableProfileLoading: this.sessionSettings.enableProfileLoading, initialWorkingDirectory: await validateCwdSetting(this.logger), shellIntegrationScript: this.shellIntegrationEnabled - ? utils.ShellIntegrationScript : "", + ? utils.ShellIntegrationScript + : "", }, errorHandler: { // Override the default error handler to prevent it from // closing the LanguageClient incorrectly when the socket // hangs up (ECONNRESET errors). - error: (_error: Error, _message: Message, _count: number): ErrorHandlerResult => { + error: ( + _error: Error, + _message: Message, + _count: number, + ): ErrorHandlerResult => { // TODO: Is there any error worth terminating on? - this.logger.writeError(`${_error.name}: ${_error.message} ${_error.cause}`); + this.logger.writeError( + `${_error.name}: ${_error.message} ${_error.cause}`, + ); return { action: ErrorAction.Continue }; }, closed: (): CloseHandlerResult => { @@ -730,18 +945,30 @@ export class SessionManager implements Middleware { // We have our own restart experience return { action: CloseAction.DoNotRestart, - message: "Connection to PowerShell Editor Services (the Extension Terminal) was closed. See below prompt to restart!" + message: + "Connection to PowerShell Editor Services (the Extension Terminal) was closed. See below prompt to restart!", }; }, }, middleware: this, - traceOutputChannel: new LanguageClientOutputChannelAdapter("PowerShell: Trace LSP", LspTraceParser), + traceOutputChannel: new LanguageClientOutputChannelAdapter( + "PowerShell: Trace LSP", + LspTraceParser, + ), // This is named the same as the Client log to merge the logs, but will be handled and disposed separately. - outputChannel: new LanguageClientOutputChannelAdapter("PowerShell", PsesParser), - revealOutputChannelOn: RevealOutputChannelOn.Never + outputChannel: new LanguageClientOutputChannelAdapter( + "PowerShell", + PsesParser, + ), + revealOutputChannelOn: RevealOutputChannelOn.Never, }; - const languageClient = new LanguageClient("powershell", "PowerShell Editor Services Client", connectFunc, clientOptions); + const languageClient = new LanguageClient( + "powershell", + "PowerShell Editor Services Client", + connectFunc, + clientOptions, + ); // This enables handling Semantic Highlighting messages in PowerShell Editor Services // TODO: We should only turn this on in preview. @@ -768,16 +995,19 @@ export class SessionManager implements Middleware { // Console.ReadKey, since it's not cancellable. On // "cancellation" the server asks us to send pretend to // press a key, thus mitigating all the quirk. - languageClient.onNotification( - SendKeyPressNotificationType, - () => { this.languageServerProcess?.sendKeyPress(); }), + languageClient.onNotification(SendKeyPressNotificationType, () => { + this.languageServerProcess?.sendKeyPress(); + }), languageClient.onNotification( ExecutionBusyStatusNotificationType, (isBusy: boolean) => { - if (isBusy) { this.setSessionBusyStatus(); } - else { this.setSessionRunningStatus(); } - } + if (isBusy) { + this.setSessionBusyStatus(); + } else { + this.setSessionRunningStatus(); + } + }, ), ]; @@ -785,7 +1015,10 @@ export class SessionManager implements Middleware { await languageClient.start(); LanguageClientConsumer.onLanguageClientStarted(languageClient); } catch (err) { - void this.setSessionFailedOpenBug("Language client failed to start: " + (err instanceof Error ? err.message : "unknown")); + void this.setSessionFailedOpenBug( + "Language client failed to start: " + + (err instanceof Error ? err.message : "unknown"), + ); } return languageClient; @@ -795,8 +1028,14 @@ export class SessionManager implements Middleware { // Because the extension is always at `/out/main.js` let bundledModulesPath = path.resolve(__dirname, "../modules"); - if (this.extensionContext.extensionMode === vscode.ExtensionMode.Development) { - const devBundledModulesPath = path.resolve(__dirname, this.sessionSettings.developer.bundledModulesPath); + if ( + this.extensionContext.extensionMode === + vscode.ExtensionMode.Development + ) { + const devBundledModulesPath = path.resolve( + __dirname, + this.sessionSettings.developer.bundledModulesPath, + ); // Make sure the module's bin path exists if (await utils.checkIfDirectoryExists(devBundledModulesPath)) { @@ -804,14 +1043,18 @@ export class SessionManager implements Middleware { } else { void this.logger.writeAndShowWarning( "In development mode but PowerShellEditorServices dev module path cannot be " + - `found (or has not been built yet): ${devBundledModulesPath}\n`); + `found (or has not been built yet): ${devBundledModulesPath}\n`, + ); } } return bundledModulesPath; } - private getEditorServicesArgs(bundledModulesPath: string, powerShellExeDetails: IPowerShellExeDetails): string { + private getEditorServicesArgs( + bundledModulesPath: string, + powerShellExeDetails: IPowerShellExeDetails, + ): string { let editorServicesArgs = "-HostName 'Visual Studio Code Host' " + "-HostProfileId 'Microsoft.VSCode' " + @@ -821,7 +1064,10 @@ export class SessionManager implements Middleware { if (this.sessionSettings.integratedConsole.suppressStartupBanner) { editorServicesArgs += "-StartupBanner '' "; - } else if (utils.isWindows && !powerShellExeDetails.supportsProperArguments) { + } else if ( + utils.isWindows && + !powerShellExeDetails.supportsProperArguments + ) { // NOTE: On Windows we don't Base64 encode the startup command // because it annoys some poorly implemented anti-virus scanners. // Unfortunately this means that for some installs of PowerShell @@ -839,28 +1085,40 @@ Type 'help' to get help. } // We guard this here too out of an abundance of precaution. - if (this.sessionSettings.developer.editorServicesWaitForDebugger - && this.extensionContext.extensionMode === vscode.ExtensionMode.Development) { + if ( + this.sessionSettings.developer.editorServicesWaitForDebugger && + this.extensionContext.extensionMode === + vscode.ExtensionMode.Development + ) { editorServicesArgs += "-WaitForDebugger "; } - const logLevel = vscode.workspace.getConfiguration("powershell.developer").get("editorServicesLogLevel"); + const logLevel = vscode.workspace + .getConfiguration("powershell.developer") + .get("editorServicesLogLevel"); editorServicesArgs += `-LogLevel '${logLevel}' `; return editorServicesArgs; } - private async getVersionDetails(): Promise { + private async getVersionDetails(): Promise< + IPowerShellVersionDetails | undefined + > { // Take one minute to get version details, otherwise cancel and fail. const timeout = new vscode.CancellationTokenSource(); - setTimeout(() => { timeout.cancel(); }, 60 * 1000); + setTimeout(() => { + timeout.cancel(); + }, 60 * 1000); const versionDetails = await this.languageClient?.sendRequest( - PowerShellVersionRequestType, timeout.token); + PowerShellVersionRequestType, + timeout.token, + ); // This is pretty much the only telemetry event we care about. // TODO: We actually could send this earlier from PSES itself. - this.sendTelemetryEvent("powershellVersionCheck", - { powershellVersion: versionDetails?.version ?? "unknown" }); + this.sendTelemetryEvent("powershellVersionCheck", { + powershellVersion: versionDetails?.version ?? "unknown", + }); return versionDetails; } @@ -871,30 +1129,45 @@ Type 'help' to get help. [ { prompt: "Yes", - action: async (): Promise => { await this.restartSession(); } + action: async (): Promise => { + await this.restartSession(); + }, }, { prompt: "No", - action: undefined - } - ] + action: undefined, + }, + ], ); } private sendTelemetryEvent( eventName: string, properties?: TelemetryEventProperties, - measures?: TelemetryEventMeasurements): void { - - if (this.extensionContext.extensionMode === vscode.ExtensionMode.Production) { - this.telemetryReporter.sendTelemetryEvent(eventName, properties, measures); + measures?: TelemetryEventMeasurements, + ): void { + if ( + this.extensionContext.extensionMode === + vscode.ExtensionMode.Production + ) { + this.telemetryReporter.sendTelemetryEvent( + eventName, + properties, + measures, + ); } } private createStatusBarItem(): vscode.LanguageStatusItem { const statusTitle = "Show PowerShell Session Menu"; - const languageStatusItem = vscode.languages.createLanguageStatusItem("powershell", this.documentSelector); - languageStatusItem.command = { title: statusTitle, command: this.ShowSessionMenuCommandName }; + const languageStatusItem = vscode.languages.createLanguageStatusItem( + "powershell", + this.documentSelector, + ); + languageStatusItem.command = { + title: statusTitle, + command: this.ShowSessionMenuCommandName, + }; languageStatusItem.text = "$(terminal-powershell)"; languageStatusItem.detail = "PowerShell"; return languageStatusItem; @@ -902,7 +1175,9 @@ Type 'help' to get help. private async waitWhileStarting(): Promise { while (this.sessionStatus === SessionStatus.Starting) { - if (this.startCancellationTokenSource?.token.isCancellationRequested) { + if ( + this.startCancellationTokenSource?.token.isCancellationRequested + ) { return; } await utils.sleep(300); @@ -916,7 +1191,9 @@ Type 'help' to get help. } private setSessionStatus(detail: string, status: SessionStatus): void { - this.logger.writeDebug(`Session status changing from '${this.sessionStatus}' to '${status}'.`); + this.logger.writeDebug( + `Session status changing from '${this.sessionStatus}' to '${status}'.`, + ); this.sessionStatus = status; this.languageStatusItem.text = "$(terminal-powershell)"; this.languageStatusItem.detail = "PowerShell"; @@ -925,10 +1202,12 @@ Type 'help' to get help. const semver = new SemVer(this.versionDetails.version); this.languageStatusItem.text += ` ${semver.major}.${semver.minor}`; this.languageStatusItem.detail += ` ${this.versionDetails.commit} (${this.versionDetails.architecture.toLowerCase()})`; - } else if (this.PowerShellExeDetails?.displayName) { // When it hasn't started yet. + } else if (this.PowerShellExeDetails?.displayName) { + // When it hasn't started yet. this.languageStatusItem.text += ` ${this.PowerShellExeDetails.displayName}`; this.languageStatusItem.detail += ` at '${this.PowerShellExeDetails.exePath}'`; - } else if (this.sessionSettings.powerShellDefaultVersion) { // When it hasn't been found yet. + } else if (this.sessionSettings.powerShellDefaultVersion) { + // When it hasn't been found yet. this.languageStatusItem.text += ` ${this.sessionSettings.powerShellDefaultVersion}`; this.languageStatusItem.detail = `Looking for '${this.sessionSettings.powerShellDefaultVersion}'...`; } @@ -938,24 +1217,28 @@ Type 'help' to get help. } switch (status) { - case SessionStatus.Running: - case SessionStatus.NotStarted: - this.languageStatusItem.busy = false; - this.languageStatusItem.severity = vscode.LanguageStatusSeverity.Information; - break; - case SessionStatus.Busy: - this.languageStatusItem.busy = true; - this.languageStatusItem.severity = vscode.LanguageStatusSeverity.Information; - break; - case SessionStatus.Starting: - case SessionStatus.Stopping: - this.languageStatusItem.busy = true; - this.languageStatusItem.severity = vscode.LanguageStatusSeverity.Warning; - break; - case SessionStatus.Failed: - this.languageStatusItem.busy = false; - this.languageStatusItem.severity = vscode.LanguageStatusSeverity.Error; - break; + case SessionStatus.Running: + case SessionStatus.NotStarted: + this.languageStatusItem.busy = false; + this.languageStatusItem.severity = + vscode.LanguageStatusSeverity.Information; + break; + case SessionStatus.Busy: + this.languageStatusItem.busy = true; + this.languageStatusItem.severity = + vscode.LanguageStatusSeverity.Information; + break; + case SessionStatus.Starting: + case SessionStatus.Stopping: + this.languageStatusItem.busy = true; + this.languageStatusItem.severity = + vscode.LanguageStatusSeverity.Warning; + break; + case SessionStatus.Failed: + this.languageStatusItem.busy = false; + this.languageStatusItem.severity = + vscode.LanguageStatusSeverity.Error; + break; } } @@ -974,43 +1257,63 @@ Type 'help' to get help. private async setSessionFailedOpenBug(message: string): Promise { this.setSessionStatus("Startup Error!", SessionStatus.Failed); - await this.logger.writeAndShowErrorWithActions(message, [{ - prompt: "Open an Issue", - action: async (): Promise => { - await vscode.commands.executeCommand("PowerShell.GenerateBugReport"); - } - }] - ); + await this.logger.writeAndShowErrorWithActions(message, [ + { + prompt: "Open an Issue", + action: async (): Promise => { + await vscode.commands.executeCommand( + "PowerShell.GenerateBugReport", + ); + }, + }, + ]); } - private async setSessionFailedGetPowerShell(message: string): Promise { + private async setSessionFailedGetPowerShell( + message: string, + ): Promise { this.setSessionStatus("Startup Error!", SessionStatus.Failed); - await this.logger.writeAndShowErrorWithActions(message, [{ - prompt: "Open PowerShell Install Documentation", - action: async (): Promise => { - await vscode.env.openExternal( - vscode.Uri.parse("https://aka.ms/get-powershell-vscode")); - } - }] - ); + await this.logger.writeAndShowErrorWithActions(message, [ + { + prompt: "Open PowerShell Install Documentation", + action: async (): Promise => { + await vscode.env.openExternal( + vscode.Uri.parse( + "https://aka.ms/get-powershell-vscode", + ), + ); + }, + }, + ]); } private async setSessionFailedGetDotNet(message: string): Promise { this.setSessionStatus("Startup Error!", SessionStatus.Failed); - await this.logger.writeAndShowErrorWithActions(message, [{ - prompt: "Open .NET Framework Documentation", - action: async (): Promise => { - await vscode.env.openExternal( - vscode.Uri.parse("https://dotnet.microsoft.com/en-us/download/dotnet-framework")); - } - }] - ); + await this.logger.writeAndShowErrorWithActions(message, [ + { + prompt: "Open .NET Framework Documentation", + action: async (): Promise => { + await vscode.env.openExternal( + vscode.Uri.parse( + "https://dotnet.microsoft.com/en-us/download/dotnet-framework", + ), + ); + }, + }, + ]); } - private async changePowerShellDefaultVersion(exePath: IPowerShellExeDetails): Promise { + private async changePowerShellDefaultVersion( + exePath: IPowerShellExeDetails, + ): Promise { this.suppressRestartPrompt = true; try { - await changeSetting("powerShellDefaultVersion", exePath.displayName, true, this.logger); + await changeSetting( + "powerShellDefaultVersion", + exePath.displayName, + true, + this.logger, + ); } finally { this.suppressRestartPrompt = false; } @@ -1025,15 +1328,26 @@ Type 'help' to get help. // Shows the temp debug terminal if it exists, otherwise the session terminal. public showDebugTerminal(isExecute?: boolean): void { if (this.debugSessionProcess) { - this.debugSessionProcess.showTerminal(isExecute && !this.sessionSettings.integratedConsole.focusConsoleOnExecute); + this.debugSessionProcess.showTerminal( + isExecute && + !this.sessionSettings.integratedConsole + .focusConsoleOnExecute, + ); } else { - this.languageServerProcess?.showTerminal(isExecute && !this.sessionSettings.integratedConsole.focusConsoleOnExecute); + this.languageServerProcess?.showTerminal( + isExecute && + !this.sessionSettings.integratedConsole + .focusConsoleOnExecute, + ); } } // Always shows the session terminal. private showSessionTerminal(isExecute?: boolean): void { - this.languageServerProcess?.showTerminal(isExecute && !this.sessionSettings.integratedConsole.focusConsoleOnExecute); + this.languageServerProcess?.showTerminal( + isExecute && + !this.sessionSettings.integratedConsole.focusConsoleOnExecute, + ); } private async showSessionMenu(): Promise { @@ -1041,47 +1355,67 @@ Type 'help' to get help. this.platformDetails, // We don't pull from session settings because we want them fresh! getSettings().powerShellAdditionalExePaths, - this.logger); - const availablePowerShellExes = await powershellExeFinder.getAllAvailablePowerShellInstallations(); + this.logger, + ); + const availablePowerShellExes = + await powershellExeFinder.getAllAvailablePowerShellInstallations(); const powerShellItems = availablePowerShellExes - .filter((item) => item.displayName !== this.PowerShellExeDetails?.displayName) + .filter( + (item) => + item.displayName !== this.PowerShellExeDetails?.displayName, + ) .map((item) => { return new SessionMenuItem( `Switch to: ${item.displayName}`, - async () => { await this.changePowerShellDefaultVersion(item); }); + async () => { + await this.changePowerShellDefaultVersion(item); + }, + ); }); const menuItems: SessionMenuItem[] = [ new SessionMenuItem( `Current session: ${this.PowerShellExeDetails?.displayName ?? "Unknown"} (click to show logs)`, - async () => { await vscode.commands.executeCommand("PowerShell.ShowLogs"); }), + async () => { + await vscode.commands.executeCommand("PowerShell.ShowLogs"); + }, + ), // Add all of the different PowerShell options ...powerShellItems, - new SessionMenuItem( - "Restart current session", - async () => { - // We pass in the display name so we guarantee that the session - // will be the same PowerShell. - if (this.PowerShellExeDetails) { - await this.restartSession(this.PowerShellExeDetails.displayName); - } else { - await this.restartSession(); - } - }), + new SessionMenuItem("Restart current session", async () => { + // We pass in the display name so we guarantee that the session + // will be the same PowerShell. + if (this.PowerShellExeDetails) { + await this.restartSession( + this.PowerShellExeDetails.displayName, + ); + } else { + await this.restartSession(); + } + }), - new SessionMenuItem( - "Open session logs folder", - async () => { await vscode.commands.executeCommand("PowerShell.OpenLogFolder"); }), + new SessionMenuItem("Open session logs folder", async () => { + await vscode.commands.executeCommand( + "PowerShell.OpenLogFolder", + ); + }), new SessionMenuItem( "Modify list of additional PowerShell locations", - async () => { await vscode.commands.executeCommand("workbench.action.openSettings", "powerShellAdditionalExePaths"); }), + async () => { + await vscode.commands.executeCommand( + "workbench.action.openSettings", + "powerShellAdditionalExePaths", + ); + }, + ), ]; - const selectedItem = await vscode.window.showQuickPick(menuItems); + const selectedItem = + await vscode.window.showQuickPick(menuItems); await selectedItem?.callback(); } } @@ -1093,6 +1427,6 @@ class SessionMenuItem implements vscode.QuickPickItem { public readonly label: string, // eslint-disable-next-line @typescript-eslint/no-empty-function - public readonly callback = async (): Promise => { }) { - } + public readonly callback = async (): Promise => {}, + ) {} } diff --git a/src/settings.ts b/src/settings.ts index 943b4c27ab..9664c10cf1 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -4,8 +4,8 @@ import vscode = require("vscode"); import utils = require("./utils"); import os = require("os"); -import type { ILogger } from "./logging"; import untildify from "untildify"; +import type { ILogger } from "./logging"; import path = require("path"); // TODO: Quite a few of these settings are unused in the client and instead @@ -17,7 +17,7 @@ import path = require("path"); // Perhaps we just get rid of this entirely? // eslint-disable-next-line @typescript-eslint/no-extraneous-class -class PartialSettings { } +class PartialSettings {} export class Settings extends PartialSettings { powerShellAdditionalExePaths: PowerShellAdditionalExePathSettings = {}; @@ -36,7 +36,7 @@ export class Settings extends PartialSettings { sideBar = new SideBarSettings(); pester = new PesterSettings(); buttons = new ButtonSettings(); - cwd = ""; // NOTE: use validateCwdSetting() instead of this directly! + cwd = ""; // NOTE: use validateCwdSetting() instead of this directly! enableReferencesCodeLens = true; analyzeOpenDocumentsOnly = false; // TODO: Add (deprecated) useX86Host (for testing) @@ -64,12 +64,12 @@ export enum CommentType { export enum StartLocation { Editor = "Editor", - Panel = "Panel" + Panel = "Panel", } -export enum ExecuteMode{ +export enum ExecuteMode { Call = "Call", - DotSource = "DotSource" + DotSource = "DotSource", } export type PowerShellAdditionalExePathSettings = Record; @@ -152,7 +152,11 @@ class ButtonSettings extends PartialSettings { } // This is a recursive function which unpacks a WorkspaceConfiguration into our settings. -function getSetting(key: string | undefined, value: TSetting, configuration: vscode.WorkspaceConfiguration): TSetting { +function getSetting( + key: string | undefined, + value: TSetting, + configuration: vscode.WorkspaceConfiguration, +): TSetting { // Base case where we're looking at a primitive type (or our special record). if (key !== undefined && !(value instanceof PartialSettings)) { return configuration.get(key, value); @@ -175,18 +179,20 @@ export function getSettings(): Settings { } // Get the ConfigurationTarget (read: scope) of where the *effective* setting value comes from -export function getEffectiveConfigurationTarget(settingName: string): vscode.ConfigurationTarget | undefined { - const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId); +export function getEffectiveConfigurationTarget( + settingName: string, +): vscode.ConfigurationTarget | undefined { + const configuration = vscode.workspace.getConfiguration( + utils.PowerShellLanguageId, + ); const detail = configuration.inspect(settingName); if (detail === undefined) { return undefined; } else if (typeof detail.workspaceFolderValue !== "undefined") { return vscode.ConfigurationTarget.WorkspaceFolder; - } - else if (typeof detail.workspaceValue !== "undefined") { + } else if (typeof detail.workspaceValue !== "undefined") { return vscode.ConfigurationTarget.Workspace; - } - else if (typeof detail.globalValue !== "undefined") { + } else if (typeof detail.globalValue !== "undefined") { return vscode.ConfigurationTarget.Global; } return undefined; @@ -197,12 +203,16 @@ export async function changeSetting( // eslint-disable-next-line @typescript-eslint/no-explicit-any newValue: any, configurationTarget: vscode.ConfigurationTarget | boolean | undefined, - logger: ILogger | undefined): Promise { - - logger?.writeDebug(`Changing '${settingName}' at scope '${configurationTarget}' to '${newValue}'.`); + logger: ILogger | undefined, +): Promise { + logger?.writeDebug( + `Changing '${settingName}' at scope '${configurationTarget}' to '${newValue}'.`, + ); try { - const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId); + const configuration = vscode.workspace.getConfiguration( + utils.PowerShellLanguageId, + ); await configuration.update(settingName, newValue, configurationTarget); } catch (err) { logger?.writeError(`Failed to change setting: ${err}`); @@ -212,14 +222,18 @@ export async function changeSetting( // We don't want to query the user more than once, so this is idempotent. let hasChosen = false; let chosenWorkspace: vscode.WorkspaceFolder | undefined = undefined; -export async function getChosenWorkspace(logger: ILogger | undefined): Promise { +export async function getChosenWorkspace( + logger: ILogger | undefined, +): Promise { if (hasChosen) { return chosenWorkspace; } // If there is no workspace, or there is but it has no folders, fallback. - if (vscode.workspace.workspaceFolders === undefined - || vscode.workspace.workspaceFolders.length === 0) { + if ( + vscode.workspace.workspaceFolders === undefined || + vscode.workspace.workspaceFolders.length === 0 + ) { chosenWorkspace = undefined; // If there is exactly one workspace folder, use that. } else if (vscode.workspace.workspaceFolders.length === 1) { @@ -227,21 +241,31 @@ export async function getChosenWorkspace(logger: ILogger | undefined): Promise 1) { const options: vscode.WorkspaceFolderPickOptions = { - placeHolder: "Select a workspace folder to use for the PowerShell Extension.", + placeHolder: + "Select a workspace folder to use for the PowerShell Extension.", }; chosenWorkspace = await vscode.window.showWorkspaceFolderPick(options); - logger?.writeDebug(`User selected workspace: '${chosenWorkspace?.name}'`); + logger?.writeDebug( + `User selected workspace: '${chosenWorkspace?.name}'`, + ); if (chosenWorkspace === undefined) { chosenWorkspace = vscode.workspace.workspaceFolders[0]; } else { const response = await vscode.window.showInformationMessage( `Would you like to save this choice by setting this workspace's 'powershell.cwd' value to '${chosenWorkspace.name}'?`, - "Yes", "No"); + "Yes", + "No", + ); if (response === "Yes") { - await changeSetting("cwd", chosenWorkspace.name, vscode.ConfigurationTarget.Workspace, logger); + await changeSetting( + "cwd", + chosenWorkspace.name, + vscode.ConfigurationTarget.Workspace, + logger, + ); } } } @@ -253,10 +277,15 @@ export async function getChosenWorkspace(logger: ILogger | undefined): Promise { - let cwd = utils.stripQuotePair( - vscode.workspace.getConfiguration(utils.PowerShellLanguageId).get("cwd")) - ?? ""; +export async function validateCwdSetting( + logger: ILogger | undefined, +): Promise { + let cwd = + utils.stripQuotePair( + vscode.workspace + .getConfiguration(utils.PowerShellLanguageId) + .get("cwd"), + ) ?? ""; // Replace ~ with home directory. cwd = untildify(cwd); @@ -264,7 +293,7 @@ export async function validateCwdSetting(logger: ILogger | undefined): Promise( options?: onSettingChangeOptions, ): vscode.Disposable { const settingPath = `${section}.${setting}`; - const disposable = vscode.workspace.onDidChangeConfiguration(e => { - if (!e.affectsConfiguration(settingPath, options?.scope)) { return; } + const disposable = vscode.workspace.onDidChangeConfiguration((e) => { + if (!e.affectsConfiguration(settingPath, options?.scope)) { + return; + } doOnSettingsChange(section, setting, action, options?.scope); if (options?.run === "once") { @@ -362,7 +392,9 @@ function doOnSettingsChange( action: (newValue: T | undefined) => void, scope?: vscode.ConfigurationScope, ): void { - const value = vscode.workspace.getConfiguration(section, scope).get(setting); + const value = vscode.workspace + .getConfiguration(section, scope) + .get(setting); action(value); } @@ -381,8 +413,7 @@ function doOnSettingsChange( export function onPowerShellSettingChange( setting: string, action: (newValue: T | undefined) => void, - options?: onSettingChangeOptions - + options?: onSettingChangeOptions, ): vscode.Disposable { const section = "powershell"; return onSettingChange(section, setting, action, options); diff --git a/src/utils.ts b/src/utils.ts index 7ea266e9c7..b1d4aadfb5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,11 +10,20 @@ export const PowerShellLanguageId = "powershell"; // Path to the shell integration script in the VS Code installation // See https://github.com/microsoft/vscode/pull/227244 -const shellIntegrationMoved = satisfies(vscode.version, ">=1.94", { includePrerelease: true }); -export const ShellIntegrationScript = path.join(vscode.env.appRoot, "out", "vs", "workbench", "contrib", "terminal", +const shellIntegrationMoved = satisfies(vscode.version, ">=1.94", { + includePrerelease: true, +}); +export const ShellIntegrationScript = path.join( + vscode.env.appRoot, + "out", + "vs", + "workbench", + "contrib", + "terminal", shellIntegrationMoved ? "common" : "browser", shellIntegrationMoved ? "scripts" : "media", - "shellIntegration.ps1"); + "shellIntegration.ps1", +); export function escapeSingleQuotes(p: string): string { return p.replace(new RegExp("'", "g"), "''"); @@ -26,8 +35,10 @@ export function stripQuotePair(p: string | undefined): string | undefined { } // Remove matching surrounding quotes from p (without regex) - if (p.startsWith("'") && p.endsWith("'") - || p.startsWith("\"") && p.endsWith("\"")) { + if ( + (p.startsWith("'") && p.endsWith("'")) || + (p.startsWith('"') && p.endsWith('"')) + ) { return p.slice(1, -1); } @@ -46,7 +57,10 @@ export function getPipePath(pipeName: string): string { // Check that the file or directory exists in an asynchronous manner that relies // solely on the VS Code API, not Node's fs library, ignoring symlinks. -async function checkIfFileOrDirectoryExists(targetPath: string | vscode.Uri, type: vscode.FileType): Promise { +async function checkIfFileOrDirectoryExists( + targetPath: string | vscode.Uri, + type: vscode.FileType, +): Promise { if (targetPath === "") { return false; } @@ -54,26 +68,37 @@ async function checkIfFileOrDirectoryExists(targetPath: string | vscode.Uri, typ const stat: vscode.FileStat = await vscode.workspace.fs.stat( targetPath instanceof vscode.Uri ? targetPath - : vscode.Uri.file(targetPath)); + : vscode.Uri.file(targetPath), + ); return (stat.type & type) !== 0; } catch { return false; } } -export async function checkIfFileExists(filePath: string | vscode.Uri): Promise { +export async function checkIfFileExists( + filePath: string | vscode.Uri, +): Promise { return await checkIfFileOrDirectoryExists(filePath, vscode.FileType.File); } -export async function checkIfDirectoryExists(directoryPath: string | vscode.Uri): Promise { - return await checkIfFileOrDirectoryExists(directoryPath, vscode.FileType.Directory); +export async function checkIfDirectoryExists( + directoryPath: string | vscode.Uri, +): Promise { + return await checkIfFileOrDirectoryExists( + directoryPath, + vscode.FileType.Directory, + ); } -export async function readDirectory(directoryPath: string | vscode.Uri): Promise { +export async function readDirectory( + directoryPath: string | vscode.Uri, +): Promise { const items = await vscode.workspace.fs.readDirectory( directoryPath instanceof vscode.Uri ? directoryPath - : vscode.Uri.file(directoryPath)); + : vscode.Uri.file(directoryPath), + ); return items.map(([name, _type]) => name); } @@ -83,7 +108,7 @@ export function getTimestampString(): string { } export function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } export const isMacOS: boolean = process.platform === "darwin"; diff --git a/test/TestEnvironment.code-workspace b/test/TestEnvironment.code-workspace index c204beb6d1..4915d16693 100644 --- a/test/TestEnvironment.code-workspace +++ b/test/TestEnvironment.code-workspace @@ -2,12 +2,12 @@ // A simple test environment that suppresses some first start warnings we don't care about. "folders": [ { - "path": "mocks" - } + "path": "mocks", + }, ], "settings": { "git.openRepositoryInParentFolders": "never", "csharp.suppressDotnetRestoreNotification": true, "extensions.ignoreRecommendations": true, - } + }, } diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts index e3d6de49a7..e68fc1a88b 100644 --- a/test/core/paths.test.ts +++ b/test/core/paths.test.ts @@ -4,20 +4,29 @@ import assert from "assert"; import * as vscode from "vscode"; import type { IPowerShellExtensionClient } from "../../src/features/ExternalApi"; +import { + checkIfDirectoryExists, + checkIfFileExists, + ShellIntegrationScript, +} from "../../src/utils"; import utils = require("../utils"); -import { checkIfDirectoryExists, checkIfFileExists, ShellIntegrationScript } from "../../src/utils"; describe("Path assumptions", function () { let globalStorageUri: vscode.Uri; let logUri: vscode.Uri; before(async () => { - const extension: IPowerShellExtensionClient = await utils.ensureEditorServicesIsConnected(); + const extension: IPowerShellExtensionClient = + await utils.ensureEditorServicesIsConnected(); globalStorageUri = extension.getStorageUri(); logUri = extension.getLogUri(); }); it("Creates the session folder at the correct path", async function () { - assert(await checkIfDirectoryExists(vscode.Uri.joinPath(globalStorageUri, "sessions"))); + assert( + await checkIfDirectoryExists( + vscode.Uri.joinPath(globalStorageUri, "sessions"), + ), + ); }); it("Creates the log folder at the correct path", async function () { diff --git a/test/core/platform.test.ts b/test/core/platform.test.ts index 30147054bc..e6de6a5f3a 100644 --- a/test/core/platform.test.ts +++ b/test/core/platform.test.ts @@ -2,12 +2,12 @@ // Licensed under the MIT License. import * as assert from "assert"; -import mockFS = require("mock-fs"); -import FileSystem = require("mock-fs/lib/filesystem"); import * as os from "os"; import * as path from "path"; import * as sinon from "sinon"; import * as platform from "../../src/platform"; +import mockFS = require("mock-fs"); +import FileSystem = require("mock-fs/lib/filesystem"); /** * Describes a platform on which the PowerShell extension should work, @@ -37,9 +37,21 @@ let additionalPowerShellExes: Record; let successAdditionalTestCases: ITestPlatformSuccessCase[]; if (process.platform === "win32") { - const msixAppDir = path.join(process.env.LOCALAPPDATA!, "Microsoft", "WindowsApps"); - const pwshMsixPath = path.join(msixAppDir, "Microsoft.PowerShell_8wekyb3d8bbwe", "pwsh.exe"); - const pwshPreviewMsixPath = path.join(msixAppDir, "Microsoft.PowerShellPreview_8wekyb3d8bbwe", "pwsh.exe"); + const msixAppDir = path.join( + process.env.LOCALAPPDATA!, + "Microsoft", + "WindowsApps", + ); + const pwshMsixPath = path.join( + msixAppDir, + "Microsoft.PowerShell_8wekyb3d8bbwe", + "pwsh.exe", + ); + const pwshPreviewMsixPath = path.join( + msixAppDir, + "Microsoft.PowerShellPreview_8wekyb3d8bbwe", + "pwsh.exe", + ); successTestCases = [ { @@ -50,51 +62,55 @@ if (process.platform === "win32") { isProcess64Bit: true, }, environmentVars: { - "ProgramFiles": "C:\\Program Files", + ProgramFiles: "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: pwshMsixPath, displayName: "PowerShell (Store)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", + exePath: + "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: pwshPreviewMsixPath, displayName: "PowerShell Preview (Store)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", + exePath: + "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true - } + supportsProperArguments: true, + }, ], filesystem: { "C:\\Program Files\\PowerShell": { @@ -126,7 +142,7 @@ if (process.platform === "win32") { }, "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { "powershell.exe": "", - } + }, }, }, { @@ -137,20 +153,22 @@ if (process.platform === "win32") { isProcess64Bit: true, }, environmentVars: { - "ProgramFiles": "C:\\Program Files", + ProgramFiles: "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -170,50 +188,54 @@ if (process.platform === "win32") { isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", + ProgramFiles: "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: pwshMsixPath, displayName: "PowerShell (Store)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", + exePath: + "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: pwshPreviewMsixPath, displayName: "PowerShell Preview (Store)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", + exePath: + "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -257,20 +279,22 @@ if (process.platform === "win32") { isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", + ProgramFiles: "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -290,35 +314,37 @@ if (process.platform === "win32") { isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", + ProgramFiles: "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: pwshMsixPath, displayName: "PowerShell (Store)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", + exePath: + "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: pwshPreviewMsixPath, displayName: "PowerShell Preview (Store)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -351,15 +377,16 @@ if (process.platform === "win32") { isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", + ProgramFiles: "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -376,27 +403,29 @@ if (process.platform === "win32") { isProcess64Bit: true, }, environmentVars: { - "USERNAME": "test", - "USERPROFILE": "C:\\Users\\test", - "ProgramFiles": "C:\\Program Files", + USERNAME: "test", + USERPROFILE: "C:\\Users\\test", + ProgramFiles: "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + windir: "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Users\\test\\.dotnet\\tools\\pwsh.exe", displayName: ".NET Core PowerShell Global Tool", - supportsProperArguments: false + supportsProperArguments: false, }, { - exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", - supportsProperArguments: true + supportsProperArguments: true, }, { - exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", + exePath: + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -408,13 +437,13 @@ if (process.platform === "win32") { ]; additionalPowerShellExes = { - "pwsh": "C:\\Users\\test\\pwsh\\pwsh.exe", + pwsh: "C:\\Users\\test\\pwsh\\pwsh.exe", "pwsh-tilde": "~\\pwsh\\pwsh.exe", "pwsh-no-exe": "C:\\Users\\test\\pwsh\\pwsh", "pwsh-folder": "C:\\Users\\test\\pwsh\\", "pwsh-folder-no-slash": "C:\\Users\\test\\pwsh", "pwsh-single-quotes": "'C:\\Users\\test\\pwsh\\pwsh.exe'", - "pwsh-double-quotes": "\"C:\\Users\\test\\pwsh\\pwsh.exe\"", + "pwsh-double-quotes": '"C:\\Users\\test\\pwsh\\pwsh.exe"', }; successAdditionalTestCases = [ @@ -426,44 +455,44 @@ if (process.platform === "win32") { isProcess64Bit: true, }, environmentVars: { - "USERNAME": "test", - "USERPROFILE": "C:\\Users\\test", + USERNAME: "test", + USERPROFILE: "C:\\Users\\test", }, expectedPowerShellSequence: [ { exePath: "C:\\Users\\test\\pwsh\\pwsh.exe", displayName: "pwsh", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: path.join(os.homedir(), "pwsh", "pwsh.exe"), displayName: "pwsh-tilde", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Users\\test\\pwsh\\pwsh.exe", displayName: "pwsh-no-exe", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Users\\test\\pwsh\\pwsh.exe", displayName: "pwsh-folder", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Users\\test\\pwsh\\pwsh.exe", displayName: "pwsh-folder-no-slash", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Users\\test\\pwsh\\pwsh.exe", displayName: "pwsh-single-quotes", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "C:\\Users\\test\\pwsh\\pwsh.exe", displayName: "pwsh-double-quotes", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { @@ -472,9 +501,9 @@ if (process.platform === "win32") { }, [path.join(os.homedir(), "pwsh")]: { "pwsh.exe": "", - } + }, }, - } + }, ]; } else { successTestCases = [ @@ -680,12 +709,12 @@ if (process.platform === "win32") { ]; additionalPowerShellExes = { - "pwsh": "/home/test/bin/pwsh", + pwsh: "/home/test/bin/pwsh", "pwsh-tilde": "~/bin/pwsh", "pwsh-folder": "/home/test/bin/", "pwsh-folder-no-slash": "/home/test/bin", "pwsh-single-quotes": "'/home/test/bin/pwsh'", - "pwsh-double-quotes": "\"/home/test/bin/pwsh\"", + "pwsh-double-quotes": '"/home/test/bin/pwsh"', }; successAdditionalTestCases = [ @@ -697,51 +726,51 @@ if (process.platform === "win32") { isProcess64Bit: true, }, environmentVars: { - "USER": "test", - "HOME": "/home/test", + USER: "test", + HOME: "/home/test", }, expectedPowerShellSequence: [ { exePath: "/home/test/bin/pwsh", displayName: "pwsh", - supportsProperArguments: true + supportsProperArguments: true, }, { // untildify ignores the HOME mock so this is platform-dependent exePath: path.join(os.homedir(), "bin", "pwsh"), displayName: "pwsh-tilde", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "/home/test/bin/pwsh", displayName: "pwsh-folder", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "/home/test/bin/pwsh", displayName: "pwsh-folder-no-slash", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "/home/test/bin/pwsh", displayName: "pwsh-single-quotes", - supportsProperArguments: true + supportsProperArguments: true, }, { exePath: "/home/test/bin/pwsh", displayName: "pwsh-double-quotes", - supportsProperArguments: true + supportsProperArguments: true, }, ], filesystem: { "/home/test/bin": { - "pwsh": "", + pwsh: "", }, [path.join(os.homedir(), "bin")]: { - "pwsh": "", - } + pwsh: "", + }, }, - } + }, ]; } @@ -772,65 +801,80 @@ function setupTestEnvironment(testPlatform: ITestPlatform): void { mockFS(testPlatform.filesystem); for (const envVar of Object.keys(testPlatform.environmentVars)) { - sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); + sinon + .stub(process.env, envVar) + .value(testPlatform.environmentVars[envVar]); } } describe("Platform module", function () { afterEach(function () { - mockFS.restore(); + mockFS.restore(); }); it("Gets the correct platform details", function () { - const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); + const platformDetails: platform.IPlatformDetails = + platform.getPlatformDetails(); switch (process.platform) { - case "darwin": - assert.strictEqual( - platformDetails.operatingSystem, - platform.OperatingSystem.MacOS, - "Platform details operating system should be MacOS"); - assert.strictEqual( - platformDetails.isProcess64Bit, - true, - "VSCode on darwin should be 64-bit"); - assert.strictEqual( - platformDetails.isOS64Bit, - true, - "Darwin is 64-bit only"); - break; - - case "linux": - assert.strictEqual( - platformDetails.operatingSystem, - platform.OperatingSystem.Linux, - "Platform details operating system should be Linux"); - assert.strictEqual( - platformDetails.isProcess64Bit, - true, - "Only 64-bit VSCode supported on Linux"); - assert.strictEqual( - platformDetails.isOS64Bit, - true, - "Only 64-bit Linux supported by PowerShell"); - return; - - case "win32": - assert.strictEqual( - platformDetails.operatingSystem, - platform.OperatingSystem.Windows, - "Platform details operating system should be Windows"); - assert.strictEqual( - platformDetails.isProcess64Bit, - process.arch === "x64" || process.arch === "arm64", - "Windows process bitness should match process arch"); - assert.strictEqual( - platformDetails.isOS64Bit, - !!(platformDetails.isProcess64Bit || process.env.ProgramW6432), - "Windows OS arch should match process bitness unless 64-bit env var set"); - return; - - default: - assert.fail("This platform is unsupported"); + case "darwin": + assert.strictEqual( + platformDetails.operatingSystem, + platform.OperatingSystem.MacOS, + "Platform details operating system should be MacOS", + ); + assert.strictEqual( + platformDetails.isProcess64Bit, + true, + "VSCode on darwin should be 64-bit", + ); + assert.strictEqual( + platformDetails.isOS64Bit, + true, + "Darwin is 64-bit only", + ); + break; + + case "linux": + assert.strictEqual( + platformDetails.operatingSystem, + platform.OperatingSystem.Linux, + "Platform details operating system should be Linux", + ); + assert.strictEqual( + platformDetails.isProcess64Bit, + true, + "Only 64-bit VSCode supported on Linux", + ); + assert.strictEqual( + platformDetails.isOS64Bit, + true, + "Only 64-bit Linux supported by PowerShell", + ); + return; + + case "win32": + assert.strictEqual( + platformDetails.operatingSystem, + platform.OperatingSystem.Windows, + "Platform details operating system should be Windows", + ); + assert.strictEqual( + platformDetails.isProcess64Bit, + process.arch === "x64" || process.arch === "arm64", + "Windows process bitness should match process arch", + ); + assert.strictEqual( + platformDetails.isOS64Bit, + !!( + platformDetails.isProcess64Bit || + process.env.ProgramW6432 + ), + "Windows OS arch should match process bitness unless 64-bit env var set", + ); + return; + + default: + assert.fail("This platform is unsupported"); } }); @@ -839,14 +883,28 @@ describe("Platform module", function () { it(`Finds it on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails, {}); + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + ); - const defaultPowerShell = await powerShellExeFinder.getFirstAvailablePowerShellInstallation(); - const expectedPowerShell = testPlatform.expectedPowerShellSequence[0]; + const defaultPowerShell = + await powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + const expectedPowerShell = + testPlatform.expectedPowerShellSequence[0]; - assert.strictEqual(defaultPowerShell!.exePath, expectedPowerShell.exePath); - assert.strictEqual(defaultPowerShell!.displayName, expectedPowerShell.displayName); - assert.strictEqual(defaultPowerShell!.supportsProperArguments, expectedPowerShell.supportsProperArguments); + assert.strictEqual( + defaultPowerShell!.exePath, + expectedPowerShell.exePath, + ); + assert.strictEqual( + defaultPowerShell!.displayName, + expectedPowerShell.displayName, + ); + assert.strictEqual( + defaultPowerShell!.supportsProperArguments, + expectedPowerShell.supportsProperArguments, + ); }); } @@ -854,9 +912,13 @@ describe("Platform module", function () { it(`Fails gracefully on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails, {}); + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + ); - const defaultPowerShell = await powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + const defaultPowerShell = + await powerShellExeFinder.getFirstAvailablePowerShellInstallation(); assert.strictEqual(defaultPowerShell, undefined); }); } @@ -867,23 +929,42 @@ describe("Platform module", function () { it(`Finds them on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails, {}); + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + ); - const foundPowerShells = await powerShellExeFinder.getAllAvailablePowerShellInstallations(); + const foundPowerShells = + await powerShellExeFinder.getAllAvailablePowerShellInstallations(); - for (let i = 0; i < testPlatform.expectedPowerShellSequence.length; i++) { + for ( + let i = 0; + i < testPlatform.expectedPowerShellSequence.length; + i++ + ) { const foundPowerShell = foundPowerShells[i]; - const expectedPowerShell = testPlatform.expectedPowerShellSequence[i]; + const expectedPowerShell = + testPlatform.expectedPowerShellSequence[i]; - assert.strictEqual(foundPowerShell.exePath, expectedPowerShell.exePath); - assert.strictEqual(foundPowerShell.displayName, expectedPowerShell.displayName); - assert.strictEqual(foundPowerShell.supportsProperArguments, expectedPowerShell.supportsProperArguments); + assert.strictEqual( + foundPowerShell.exePath, + expectedPowerShell.exePath, + ); + assert.strictEqual( + foundPowerShell.displayName, + expectedPowerShell.displayName, + ); + assert.strictEqual( + foundPowerShell.supportsProperArguments, + expectedPowerShell.supportsProperArguments, + ); } assert.strictEqual( foundPowerShells.length, testPlatform.expectedPowerShellSequence.length, - "Number of expected PowerShells found does not match"); + "Number of expected PowerShells found does not match", + ); }); } @@ -891,18 +972,24 @@ describe("Platform module", function () { it(`Fails gracefully on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails, {}); + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + ); - const foundPowerShells = await powerShellExeFinder.getAllAvailablePowerShellInstallations(); + const foundPowerShells = + await powerShellExeFinder.getAllAvailablePowerShellInstallations(); assert.strictEqual(foundPowerShells.length, 0); }); } }); describe("Windows PowerShell path fix", function () { - for (const testPlatform of successTestCases - .filter((tp) => tp.platformDetails.operatingSystem === platform.OperatingSystem.Windows)) { - + for (const testPlatform of successTestCases.filter( + (tp) => + tp.platformDetails.operatingSystem === + platform.OperatingSystem.Windows, + )) { it(`Corrects the Windows PowerShell path on ${testPlatform.name}`, function () { setupTestEnvironment(testPlatform); @@ -912,7 +999,8 @@ describe("Platform module", function () { systemDir, "WindowsPowerShell", "v1.0", - "powershell.exe"); + "powershell.exe", + ); } const winPSPath = getWinPSPath("System32"); @@ -926,12 +1014,23 @@ describe("Platform module", function () { altWinPSPath = null; } - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails, {}); + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + ); - assert.strictEqual(powerShellExeFinder.fixWindowsPowerShellPath(winPSPath), winPSPath); + assert.strictEqual( + powerShellExeFinder.fixWindowsPowerShellPath(winPSPath), + winPSPath, + ); if (altWinPSPath) { - assert.strictEqual(powerShellExeFinder.fixWindowsPowerShellPath(altWinPSPath), winPSPath); + assert.strictEqual( + powerShellExeFinder.fixWindowsPowerShellPath( + altWinPSPath, + ), + winPSPath, + ); } }); } @@ -942,15 +1041,25 @@ describe("Platform module", function () { it(`Guesses for ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails, additionalPowerShellExes); + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + additionalPowerShellExes, + ); let i = 0; for await (const additionalPwsh of powerShellExeFinder.enumerateAdditionalPowerShellInstallations()) { - const expectedPowerShell = testPlatform.expectedPowerShellSequence[i]; + const expectedPowerShell = + testPlatform.expectedPowerShellSequence[i]; i++; - assert.strictEqual(additionalPwsh.exePath, expectedPowerShell.exePath); - assert.strictEqual(additionalPwsh.displayName, expectedPowerShell.displayName); + assert.strictEqual( + additionalPwsh.exePath, + expectedPowerShell.exePath, + ); + assert.strictEqual( + additionalPwsh.displayName, + expectedPowerShell.displayName, + ); } }); } diff --git a/test/core/settings.test.ts b/test/core/settings.test.ts index 306872a2dc..91a98a6dc7 100644 --- a/test/core/settings.test.ts +++ b/test/core/settings.test.ts @@ -3,21 +3,26 @@ import * as assert from "assert"; import * as os from "os"; +import path from "path"; import * as vscode from "vscode"; import { - Settings, - getSettings, - getEffectiveConfigurationTarget, changeSetting, CommentType, - validateCwdSetting + getEffectiveConfigurationTarget, + getSettings, + Settings, + validateCwdSetting, } from "../../src/settings"; -import path from "path"; import { ensureEditorServicesIsConnected } from "../utils"; describe("Settings E2E", function () { async function changeCwdSetting(cwd: string | undefined): Promise { - await changeSetting("cwd", cwd, vscode.ConfigurationTarget.Workspace, undefined); + await changeSetting( + "cwd", + cwd, + vscode.ConfigurationTarget.Workspace, + undefined, + ); } async function resetCwdSetting(): Promise { @@ -40,20 +45,38 @@ describe("Settings E2E", function () { describe("The 'changeSetting' method", function () { it("Updates correctly", async function () { - await changeSetting("helpCompletion", CommentType.LineComment, vscode.ConfigurationTarget.Workspace, undefined); - assert.strictEqual(getSettings().helpCompletion, CommentType.LineComment); + await changeSetting( + "helpCompletion", + CommentType.LineComment, + vscode.ConfigurationTarget.Workspace, + undefined, + ); + assert.strictEqual( + getSettings().helpCompletion, + CommentType.LineComment, + ); }); }); describe("The 'getEffectiveConfigurationTarget' method'", function () { it("Works for 'Workspace' target", async function () { - await changeSetting("helpCompletion", CommentType.LineComment, vscode.ConfigurationTarget.Workspace, undefined); + await changeSetting( + "helpCompletion", + CommentType.LineComment, + vscode.ConfigurationTarget.Workspace, + undefined, + ); const target = getEffectiveConfigurationTarget("helpCompletion"); assert.strictEqual(target, vscode.ConfigurationTarget.Workspace); }); it("Works for 'undefined' target", async function () { - await changeSetting("helpCompletion", undefined, vscode.ConfigurationTarget.Workspace, undefined); + await changeSetting( + "helpCompletion", + undefined, + vscode.ConfigurationTarget.Workspace, + undefined, + ); const target = getEffectiveConfigurationTarget("helpCompletion"); assert.strictEqual(target, undefined); }); @@ -85,7 +108,10 @@ describe("Settings E2E", function () { it("Uses the home folder for ~ (tilde)", async function () { await changeCwdSetting("~"); - assert.strictEqual(await validateCwdSetting(undefined), os.homedir()); + assert.strictEqual( + await validateCwdSetting(undefined), + os.homedir(), + ); }); it("Accepts relative paths", async function () { diff --git a/test/features/DebugSession.test.ts b/test/features/DebugSession.test.ts index dfc9a5806e..815e5155f5 100644 --- a/test/features/DebugSession.test.ts +++ b/test/features/DebugSession.test.ts @@ -5,45 +5,53 @@ import structuredClone from "@ungap/structured-clone"; //Polyfill for structured import * as assert from "assert"; import Sinon from "sinon"; import { - DebugAdapterNamedPipeServer, - type DebugConfiguration, - type DebugSession, - type Extension, - type ExtensionContext, - Range, - SourceBreakpoint, - type TextDocument, - type TextEditor, - Uri, - commands, - debug, - extensions, - window, - workspace, + DebugAdapterNamedPipeServer, + type DebugConfiguration, + type DebugSession, + type Extension, + type ExtensionContext, + Range, + SourceBreakpoint, + type TextDocument, + type TextEditor, + Uri, + commands, + debug, + extensions, + window, + workspace, } from "vscode"; import { Disposable } from "vscode-languageserver-protocol"; import { - DebugConfig, - DebugSessionFeature, - DebugConfigurations, + DebugConfig, + DebugConfigurations, + DebugSessionFeature, } from "../../src/features/DebugSession"; import type { IPowerShellExtensionClient } from "../../src/features/ExternalApi"; -import * as platform from "../../src/platform"; import type { IPlatformDetails } from "../../src/platform"; +import * as platform from "../../src/platform"; import { - type IEditorServicesSessionDetails, - type IPowerShellVersionDetails, - SessionManager, + type IEditorServicesSessionDetails, + type IPowerShellVersionDetails, + SessionManager, } from "../../src/session"; import * as utils from "../../src/utils"; -import { BuildBinaryModuleMock, WaitEvent, ensureEditorServicesIsConnected, stubInterface, testLogger } from "../utils"; +import { + BuildBinaryModuleMock, + WaitEvent, + ensureEditorServicesIsConnected, + stubInterface, + testLogger, +} from "../utils"; const TEST_NUMBER = 7357; // 7357 = TEST. Get it? :) let defaultDebugConfig: DebugConfiguration; beforeEach(() => { // This prevents state from creeping into the template between test runs - defaultDebugConfig = structuredClone(DebugConfigurations[DebugConfig.LaunchCurrentFile]); + defaultDebugConfig = structuredClone( + DebugConfigurations[DebugConfig.LaunchCurrentFile], + ); }); describe("DebugSessionFeature", () => { @@ -56,10 +64,10 @@ describe("DebugSessionFeature", () => { */ function createDebugSessionFeatureStub({ context = stubInterface({ - subscriptions: Array() //Needed for constructor + subscriptions: Array(), //Needed for constructor }), sessionManager = Sinon.createStubInstance(SessionManager), - logger = testLogger + logger = testLogger, }): DebugSessionFeature { return new DebugSessionFeature(context, sessionManager, logger); } @@ -69,15 +77,33 @@ describe("DebugSessionFeature", () => { document: stubInterface({ uri: Uri.parse("file:///fakeUntitled.ps1"), languageId: "powershell", - isUntitled: true - }) + isUntitled: true, + }), }); beforeEach(() => { // Because we recreate DebugSessionFeature constantly, we need to avoid registering the same commands over and over. - Sinon.stub(commands, "registerCommand").returns(Disposable.create(() => {"Stubbed";})); - registerProviderStub = Sinon.stub(debug, "registerDebugConfigurationProvider").returns(Disposable.create(() => {"Stubbed";})); - registerFactoryStub = Sinon.stub(debug, "registerDebugAdapterDescriptorFactory").returns(Disposable.create(() => {"Stubbed";})); + Sinon.stub(commands, "registerCommand").returns( + Disposable.create(() => { + "Stubbed"; + }), + ); + registerProviderStub = Sinon.stub( + debug, + "registerDebugConfigurationProvider", + ).returns( + Disposable.create(() => { + "Stubbed"; + }), + ); + registerFactoryStub = Sinon.stub( + debug, + "registerDebugAdapterDescriptorFactory", + ).returns( + Disposable.create(() => { + "Stubbed"; + }), + ); }); afterEach(() => { @@ -87,13 +113,23 @@ describe("DebugSessionFeature", () => { describe("Constructor", () => { it("Registers debug configuration provider and factory", () => { const context = stubInterface({ - subscriptions: Array() + subscriptions: Array(), }); - createDebugSessionFeatureStub({context: context}); - assert.ok(registerFactoryStub.calledOnce, "Debug adapter factory method called"); - assert.ok(registerProviderStub.calledTwice, "Debug config provider registered for both Initial and Dynamic"); - assert.equal(context.subscriptions.length, 4, "DebugSessionFeature disposables populated"); + createDebugSessionFeatureStub({ context: context }); + assert.ok( + registerFactoryStub.calledOnce, + "Debug adapter factory method called", + ); + assert.ok( + registerProviderStub.calledTwice, + "Debug config provider registered for both Initial and Dynamic", + ); + assert.equal( + context.subscriptions.length, + 4, + "DebugSessionFeature disposables populated", + ); // TODO: Validate the registration content, such as the language name }); }); @@ -105,26 +141,38 @@ describe("DebugSessionFeature", () => { // Need to have an editor window "open" for this not to error out Sinon.stub(window, "activeTextEditor").value(untitledEditor); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfiguration(undefined, noRequestConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfiguration(undefined, noRequestConfig); assert.equal(actual!.current_document, true); - assert.equal(actual!.request, DebugConfigurations[DebugConfig.LaunchCurrentFile].request); + assert.equal( + actual!.request, + DebugConfigurations[DebugConfig.LaunchCurrentFile].request, + ); }); it("Errors if current file config was specified but no file is open in the editor", async () => { Sinon.stub(window, "activeTextEditor").value(undefined); const logger = Sinon.stub(testLogger); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfiguration(undefined, defaultDebugConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfiguration(undefined, defaultDebugConfig); assert.equal(actual!, undefined); - assert.match(logger.writeAndShowError.firstCall.args[0], /you must first open a PowerShell script file/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /you must first open a PowerShell script file/, + ); }); it("Detects an untitled document", async () => { Sinon.stub(window, "activeTextEditor").value(untitledEditor); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfiguration(undefined, defaultDebugConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfiguration(undefined, defaultDebugConfig); assert.equal(actual!.untitled_document, true); assert.equal(actual!.script, "file:///fakeUntitled.ps1"); @@ -135,7 +183,12 @@ describe("DebugSessionFeature", () => { it("Sets internalConsoleOptions to neverOpen", async () => { Sinon.stub(window, "activeTextEditor").value(untitledEditor); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, defaultDebugConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + defaultDebugConfig, + ); assert.equal(actual!.internalConsoleOptions, "neverOpen"); }); @@ -144,19 +197,40 @@ describe("DebugSessionFeature", () => { invalidRequestConfig.request = "notAttachOrLaunch"; const logger = Sinon.stub(testLogger); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, invalidRequestConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + invalidRequestConfig, + ); assert.equal(actual, null); - assert.match(logger.writeAndShowError.firstCall.args[0], /request type was invalid/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /request type was invalid/, + ); }); it("Uses createTemporaryIntegratedConsole config setting if not explicitly specified", async () => { Sinon.stub(window, "activeTextEditor").value(untitledEditor); - assert.equal(defaultDebugConfig.createTemporaryIntegratedConsole, undefined, "Default config should have no temp integrated console setting"); + assert.equal( + defaultDebugConfig.createTemporaryIntegratedConsole, + undefined, + "Default config should have no temp integrated console setting", + ); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, defaultDebugConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + defaultDebugConfig, + ); - assert.notEqual(actual!.createTemporaryIntegratedConsole, undefined, "createTemporaryIntegratedConsole should have received a value from the settings and no longer be undefined"); + assert.notEqual( + actual!.createTemporaryIntegratedConsole, + undefined, + "createTemporaryIntegratedConsole should have received a value from the settings and no longer be undefined", + ); }); it("LaunchCurrentFile: Rejects non-Powershell language active editor", async () => { @@ -164,18 +238,26 @@ describe("DebugSessionFeature", () => { document: stubInterface({ uri: Uri.parse("file:///fakeUntitled.ps1"), languageId: "NotPowerShell", - isUntitled: true - }) + isUntitled: true, + }), }); const currentDocConfig: DebugConfiguration = defaultDebugConfig; currentDocConfig.current_document = true; Sinon.stub(window, "activeTextEditor").value(nonPSEditor); const logger = Sinon.stub(testLogger); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, currentDocConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + currentDocConfig, + ); assert.equal(actual, undefined, "Debug session should end"); - assert.match(logger.writeAndShowError.firstCall.args[0], /debugging this language mode/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /debugging this language mode/, + ); }); // Skipped until we can fix the stub @@ -188,10 +270,18 @@ describe("DebugSessionFeature", () => { Sinon.stub(utils, "checkIfFileExists").resolves(true); const logger = Sinon.stub(testLogger); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, currentDocConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + currentDocConfig, + ); assert.equal(actual, undefined); - assert.match(logger.writeAndShowError.firstCall.args[0], /debugging this file type/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /debugging this file type/, + ); Sinon.restore(); }); @@ -202,13 +292,21 @@ describe("DebugSessionFeature", () => { Sinon.stub(window, "activeTextEditor").value(untitledEditor); const logger = Sinon.stub(testLogger); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, currentDocConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + currentDocConfig, + ); assert.equal(actual, undefined); - assert.match(logger.writeAndShowError.firstCall.args[0], /debugging untitled/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /debugging untitled/, + ); }); - it("Attach: Exits if session version details cannot be retrieved", async () => { + it("Attach: Exits if session version details cannot be retrieved", async () => { const attachConfig: DebugConfiguration = defaultDebugConfig; attachConfig.request = "attach"; const logger = Sinon.stub(testLogger); @@ -216,37 +314,49 @@ describe("DebugSessionFeature", () => { sessionManager.getPowerShellVersionDetails.returns(undefined); const actual = await createDebugSessionFeatureStub({ - sessionManager: sessionManager - }).resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + sessionManager: sessionManager, + }).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual, undefined); - assert.match(logger.writeAndShowError.firstCall.args[0], /session version details were not found/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /session version details were not found/, + ); assert.ok(sessionManager.getPowerShellVersionDetails.calledOnce); }); // Skipped until we can fix the stub - it.skip("Attach: Prevents attach on non-windows if not PS7.0 or higher", async() => { + it.skip("Attach: Prevents attach on non-windows if not PS7.0 or higher", async () => { const attachConfig: DebugConfiguration = defaultDebugConfig; attachConfig.request = "attach"; const logger = Sinon.stub(testLogger); const sessionManager = Sinon.createStubInstance(SessionManager, {}); Sinon.stub(platform, "getPlatformDetails").returns( stubInterface({ - operatingSystem: platform.OperatingSystem.MacOS - }) + operatingSystem: platform.OperatingSystem.MacOS, + }), ); sessionManager.getPowerShellVersionDetails.returns( stubInterface({ - version: "6.2.3" - }) + version: "6.2.3", + }), ); const actual = await createDebugSessionFeatureStub({ - sessionManager: sessionManager - }).resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + sessionManager: sessionManager, + }).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual, undefined); - assert.match(logger.writeAndShowError.firstCall.args[0], /requires PowerShell 7/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /requires PowerShell 7/, + ); assert.ok(sessionManager.getPowerShellVersionDetails.calledOnce); }); @@ -259,17 +369,24 @@ describe("DebugSessionFeature", () => { const sessionManager = Sinon.createStubInstance(SessionManager, {}); sessionManager.getPowerShellVersionDetails.returns( stubInterface({ - version: "7.2.3" - }) + version: "7.2.3", + }), ); const debugSessionFeatureStub = createDebugSessionFeatureStub({ - sessionManager: sessionManager + sessionManager: sessionManager, }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const pickPSHostProcessStub = Sinon.stub(debugSessionFeatureStub , "pickPSHostProcess" as any).resolves(7357); + const pickPSHostProcessStub = Sinon.stub( + debugSessionFeatureStub, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "pickPSHostProcess" as any, + ).resolves(7357); - const actual = await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const actual = + await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual!.processId, TEST_NUMBER); assert.ok(pickPSHostProcessStub.calledOnce); @@ -284,17 +401,24 @@ describe("DebugSessionFeature", () => { const sessionManager = Sinon.createStubInstance(SessionManager, {}); sessionManager.getPowerShellVersionDetails.returns( stubInterface({ - version: "7.2.3" - }) + version: "7.2.3", + }), ); const debugSessionFeatureStub = createDebugSessionFeatureStub({ - sessionManager: sessionManager + sessionManager: sessionManager, }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const pickPSHostProcessStub = Sinon.stub(debugSessionFeatureStub, "pickPSHostProcess" as any).resolves(undefined); + const pickPSHostProcessStub = Sinon.stub( + debugSessionFeatureStub, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "pickPSHostProcess" as any, + ).resolves(undefined); - const actual = await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const actual = + await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual, undefined); assert.ok(pickPSHostProcessStub.calledOnce); @@ -308,17 +432,24 @@ describe("DebugSessionFeature", () => { const sessionManager = Sinon.createStubInstance(SessionManager, {}); sessionManager.getPowerShellVersionDetails.returns( stubInterface({ - version: "7.2.3" - }) + version: "7.2.3", + }), ); const debugSessionFeatureStub = createDebugSessionFeatureStub({ - sessionManager: sessionManager + sessionManager: sessionManager, }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const pickRunspaceStub = Sinon.stub(debugSessionFeatureStub, "pickRunspace" as any).resolves(TEST_NUMBER); + const pickRunspaceStub = Sinon.stub( + debugSessionFeatureStub, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "pickRunspace" as any, + ).resolves(TEST_NUMBER); - const actual = await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const actual = + await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual!.runspaceId, TEST_NUMBER); assert.ok(pickRunspaceStub.calledOnceWith(TEST_NUMBER)); @@ -332,17 +463,24 @@ describe("DebugSessionFeature", () => { const sessionManager = Sinon.createStubInstance(SessionManager, {}); sessionManager.getPowerShellVersionDetails.returns( stubInterface({ - version: "7.2.3" - }) + version: "7.2.3", + }), ); const debugSessionFeatureStub = createDebugSessionFeatureStub({ - sessionManager: sessionManager + sessionManager: sessionManager, }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const pickRunspaceStub = Sinon.stub(debugSessionFeatureStub, "pickRunspace" as any).resolves(undefined); - - const actual = await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const pickRunspaceStub = Sinon.stub( + debugSessionFeatureStub, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "pickRunspace" as any, + ).resolves(undefined); + + const actual = + await debugSessionFeatureStub.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual, undefined); assert.ok(pickRunspaceStub.calledOnceWith(TEST_NUMBER)); }); @@ -353,13 +491,21 @@ describe("DebugSessionFeature", () => { attachConfig.createTemporaryIntegratedConsole = true; attachConfig.attachDotnetDebugger = true; Sinon.stub(extensions, "getExtension").returns( - stubInterface>() + stubInterface>(), ); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); const dotnetAttachConfig = actual!.dotnetAttachConfig; - assert.equal(dotnetAttachConfig.name, "Dotnet Debugger: Temporary Extension Terminal"); + assert.equal( + dotnetAttachConfig.name, + "Dotnet Debugger: Temporary Extension Terminal", + ); assert.equal(dotnetAttachConfig.request, "attach"); assert.equal(dotnetAttachConfig.type, "coreclr"); assert.equal(dotnetAttachConfig.processId, undefined); @@ -372,10 +518,18 @@ describe("DebugSessionFeature", () => { attachConfig.attachDotnetDebugger = true; const logger = Sinon.stub(testLogger); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual!, null); - assert.match(logger.writeAndShowError.firstCall.args[0], /dotnet debugging without using a temporary console/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /dotnet debugging without using a temporary console/, + ); }); it("Errors if dotnetDebuggerConfigName was provided but the config was not found", async () => { @@ -386,13 +540,21 @@ describe("DebugSessionFeature", () => { attachConfig.dotnetDebuggerConfigName = "not a real config"; const logger = Sinon.stub(testLogger); Sinon.stub(extensions, "getExtension").returns( - stubInterface>() + stubInterface>(), ); - const actual = await createDebugSessionFeatureStub({}).resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const actual = await createDebugSessionFeatureStub( + {}, + ).resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); assert.equal(actual!, null); - assert.match(logger.writeAndShowError.firstCall.args[0], /matching launch config was not found/); + assert.match( + logger.writeAndShowError.firstCall.args[0], + /matching launch config was not found/, + ); }); it("Finds the correct dotnetDebuggerConfigName", async () => { @@ -405,29 +567,30 @@ describe("DebugSessionFeature", () => { { name: "BadCandidate1", type: "powershell", - request: "attach" + request: "attach", }, { name: "BadCandidate2", type: "coreclr", - request: "attach" + request: "attach", }, - { // This one has launch instead of attach and even tho it has same name, should not be matched + { + // This one has launch instead of attach and even tho it has same name, should not be matched name: foundDotnetConfig.name, type: "coreclr", - request: "launch" + request: "launch", }, foundDotnetConfig, //This is the one we want to match { name: foundDotnetConfig.name, type: "notcoreclrExactly", - request: "attach" + request: "attach", }, { name: `${foundDotnetConfig.name}notexactlythisname`, type: "coreclr", - request: "attach" - } + request: "attach", + }, ]; const attachConfig = defaultDebugConfig; attachConfig.script = "test.ps1"; // This bypasses the ${file} logic @@ -436,15 +599,25 @@ describe("DebugSessionFeature", () => { attachConfig.dotnetDebuggerConfigName = foundDotnetConfig.name; const debugSessionFeature = createDebugSessionFeatureStub({}); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Sinon.stub(debugSessionFeature, "getLaunchConfigurations" as any).returns(candidateDotnetConfigs); + Sinon.stub( + debugSessionFeature, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "getLaunchConfigurations" as any, + ).returns(candidateDotnetConfigs); - const config = await debugSessionFeature.resolveDebugConfigurationWithSubstitutedVariables(undefined, attachConfig); + const config = + await debugSessionFeature.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + attachConfig, + ); // This config will only be present if the C# extension is installed. if (extensions.getExtension("ms-dotnettools.csharp")) { assert.ok(config); - assert.deepStrictEqual(config.dotnetAttachConfig, foundDotnetConfig); + assert.deepStrictEqual( + config.dotnetAttachConfig, + foundDotnetConfig, + ); } else { assert.ok(!config); } @@ -455,27 +628,34 @@ describe("DebugSessionFeature", () => { it("Creates a named pipe server for the debug adapter", async () => { const debugSessionFeature = createDebugSessionFeatureStub({ sessionManager: Sinon.createStubInstance(SessionManager, { - getSessionDetails: stubInterface({ - debugServicePipeName: "testPipeName" - }) + getSessionDetails: + stubInterface({ + debugServicePipeName: "testPipeName", + }), }), }); const debugSession = stubInterface({ configuration: stubInterface({ - createTemporaryIntegratedConsole: false - }) + createTemporaryIntegratedConsole: false, + }), }); - const debugAdapterDescriptor = await debugSessionFeature.createDebugAdapterDescriptor(debugSession, undefined); + const debugAdapterDescriptor = + await debugSessionFeature.createDebugAdapterDescriptor( + debugSession, + undefined, + ); // Confirm debugAdapterDescriptor is of type debugadapternamedpipeserver - assert.ok(debugAdapterDescriptor instanceof DebugAdapterNamedPipeServer); + assert.ok( + debugAdapterDescriptor instanceof DebugAdapterNamedPipeServer, + ); assert.equal(debugAdapterDescriptor.path, "testPipeName"); }); }); }); -describe("DebugSessionFeature E2E", function() { +describe("DebugSessionFeature E2E", function () { // E2E tests can take a while to run since the debugger has to start up and attach this.slow(20000); before(async () => { @@ -484,7 +664,6 @@ describe("DebugSessionFeature E2E", function() { }); it("Starts and stops a debugging session", async () => { - // Inspect the debug session via the started events to ensure it is correct const startDebugSession = new Promise((resolve) => { const event = debug.onDidStartDebugSession((session) => { @@ -500,13 +679,28 @@ describe("DebugSessionFeature E2E", function() { }); const config = DebugConfigurations[DebugConfig.InteractiveSession]; - assert.ok(await debug.startDebugging(undefined, config), "Debug session should start"); - assert.equal((await startDebugSession).name, config.name, "Debug session name should match when started"); + assert.ok( + await debug.startDebugging(undefined, config), + "Debug session should start", + ); + assert.equal( + (await startDebugSession).name, + config.name, + "Debug session name should match when started", + ); await debug.stopDebugging(await startDebugSession); assert.ok(await stopDebugSession, "Debug session should stop"); - assert.equal((await stopDebugSession).name, config.name, "Debug session name should match when stopped"); - assert.equal((await stopDebugSession).configuration.internalConsoleOptions, "neverOpen", "Debug session should always have neverOpen internalConsoleOptions"); + assert.equal( + (await stopDebugSession).name, + config.name, + "Debug session name should match when stopped", + ); + assert.equal( + (await stopDebugSession).configuration.internalConsoleOptions, + "neverOpen", + "Debug session should always have neverOpen internalConsoleOptions", + ); }); describe("Binary Modules", () => { @@ -517,7 +711,10 @@ describe("DebugSessionFeature E2E", function() { // These tests require that extension to be installed in the test environment. this.skip(); } - binaryModulePath = Uri.joinPath(workspace.workspaceFolders![0].uri, "BinaryModule"); + binaryModulePath = Uri.joinPath( + workspace.workspaceFolders![0].uri, + "BinaryModule", + ); BuildBinaryModuleMock(); await ensureEditorServicesIsConnected(); }); @@ -530,13 +727,21 @@ describe("DebugSessionFeature E2E", function() { }); it("Debugs a binary module script", async () => { - const launchScriptConfig = structuredClone(DebugConfigurations[DebugConfig.LaunchScript]); - launchScriptConfig.script = Uri.joinPath(binaryModulePath, "BinaryModuleTest.ps1").fsPath; + const launchScriptConfig = structuredClone( + DebugConfigurations[DebugConfig.LaunchScript], + ); + launchScriptConfig.script = Uri.joinPath( + binaryModulePath, + "BinaryModuleTest.ps1", + ).fsPath; launchScriptConfig.attachDotnetDebugger = true; launchScriptConfig.createTemporaryIntegratedConsole = true; const startDebugging = Sinon.spy(debug, "startDebugging"); - const debugStarted = await debug.startDebugging(undefined, launchScriptConfig); + const debugStarted = await debug.startDebugging( + undefined, + launchScriptConfig, + ); assert.ok(debugStarted); await debug.stopDebugging(undefined); @@ -544,38 +749,62 @@ describe("DebugSessionFeature E2E", function() { assert.ok(startDebugging.calledTwice); assert.ok(startDebugging.calledWith(undefined, launchScriptConfig)); // The C# child process - assert.ok(startDebugging.calledWithMatch( - undefined, - Sinon.match.has("type", "coreclr"), // The new child debugger - Sinon.match.has("type", "PowerShell") // The parent session - ), "The C# debugger child process is created with the PowerShell debugger as the parent"); + assert.ok( + startDebugging.calledWithMatch( + undefined, + Sinon.match.has("type", "coreclr"), // The new child debugger + Sinon.match.has("type", "PowerShell"), // The parent session + ), + "The C# debugger child process is created with the PowerShell debugger as the parent", + ); }); it("Stops at a binary module breakpoint", async () => { - const launchScriptConfig = structuredClone(DebugConfigurations[DebugConfig.LaunchCurrentFile]); + const launchScriptConfig = structuredClone( + DebugConfigurations[DebugConfig.LaunchCurrentFile], + ); launchScriptConfig.attachDotnetDebugger = true; launchScriptConfig.createTemporaryIntegratedConsole = true; - const testScriptPath = Uri.joinPath(binaryModulePath, "BinaryModuleTest.ps1"); - const cmdletSourcePath = Uri.joinPath(binaryModulePath, "TestSampleCmdletCommand.cs"); - const testScriptDocument = await workspace.openTextDocument(testScriptPath); + const testScriptPath = Uri.joinPath( + binaryModulePath, + "BinaryModuleTest.ps1", + ); + const cmdletSourcePath = Uri.joinPath( + binaryModulePath, + "TestSampleCmdletCommand.cs", + ); + const testScriptDocument = + await workspace.openTextDocument(testScriptPath); await window.showTextDocument(testScriptDocument); // We cant see when a breakpoint is hit because the code we would spy on is in the C# extension or is vscode private, but we can see if the debug session changes which should only happen when the debug session context switches to C#, so that's good enough. //We wire this up before starting the debug session so the event is registered - const dotnetDebugSessionActive = WaitEvent(debug.onDidChangeActiveDebugSession, (session) => { - return !!session?.name.match(/Dotnet Debugger/); - }); + const dotnetDebugSessionActive = WaitEvent( + debug.onDidChangeActiveDebugSession, + (session) => { + return !!session?.name.match(/Dotnet Debugger/); + }, + ); // Break at beginProcessing of the cmdlet debug.addBreakpoints([ - new SourceBreakpoint({ - uri: cmdletSourcePath, - range: new Range(26, 0, 26, 0) //BeginProcessing - }, true, undefined, undefined, "TEST-BinaryModuleBreakpoint") + new SourceBreakpoint( + { + uri: cmdletSourcePath, + range: new Range(26, 0, 26, 0), //BeginProcessing + }, + true, + undefined, + undefined, + "TEST-BinaryModuleBreakpoint", + ), ]); - const debugStarted = await debug.startDebugging(undefined, launchScriptConfig); + const debugStarted = await debug.startDebugging( + undefined, + launchScriptConfig, + ); console.log(debug.breakpoints); const dotnetDebugSession = await dotnetDebugSessionActive; console.log(debug.activeDebugSession); diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts index 6d82b17cba..d0b3e56641 100644 --- a/test/features/ExternalApi.test.ts +++ b/test/features/ExternalApi.test.ts @@ -2,8 +2,11 @@ // Licensed under the MIT License. import * as assert from "assert"; +import type { + IExternalPowerShellDetails, + IPowerShellExtensionClient, +} from "../../src/features/ExternalApi"; import utils = require("../utils"); -import type { IExternalPowerShellDetails, IPowerShellExtensionClient } from "../../src/features/ExternalApi"; describe("ExternalApi feature", function () { describe("External extension registration", function () { @@ -13,35 +16,48 @@ describe("ExternalApi feature", function () { }); it("Registers and unregisters an extension", function () { - const sessionId: string = extension.registerExternalExtension(utils.extensionId); + const sessionId: string = extension.registerExternalExtension( + utils.extensionId, + ); assert.notStrictEqual(sessionId, ""); assert.notStrictEqual(sessionId, null); assert.strictEqual( extension.unregisterExternalExtension(sessionId), - true); + true, + ); }); it("Registers and unregisters an extension with a version", function () { - const sessionId: string = extension.registerExternalExtension(utils.extensionId, "v2"); + const sessionId: string = extension.registerExternalExtension( + utils.extensionId, + "v2", + ); assert.notStrictEqual(sessionId, ""); assert.notStrictEqual(sessionId, null); assert.strictEqual( extension.unregisterExternalExtension(sessionId), - true); + true, + ); }); it("Rejects if not registered", async function () { - await assert.rejects(async () => await extension.getPowerShellVersionDetails("")); + await assert.rejects( + async () => await extension.getPowerShellVersionDetails(""), + ); }); it("Throws if attempting to register an extension more than once", function () { - const sessionId: string = extension.registerExternalExtension(utils.extensionId); + const sessionId: string = extension.registerExternalExtension( + utils.extensionId, + ); try { assert.throws( - () => extension.registerExternalExtension(utils.extensionId), + () => + extension.registerExternalExtension(utils.extensionId), { - message: `The extension '${utils.extensionId}' is already registered.` - }); + message: `The extension '${utils.extensionId}' is already registered.`, + }, + ); } finally { extension.unregisterExternalExtension(sessionId); } @@ -51,8 +67,10 @@ describe("ExternalApi feature", function () { assert.throws( () => extension.unregisterExternalExtension("not-real"), { - message: "No extension registered with session UUID: not-real" - }); + message: + "No extension registered with session UUID: not-real", + }, + ); }); }); @@ -65,10 +83,13 @@ describe("ExternalApi feature", function () { sessionId = extension.registerExternalExtension(utils.extensionId); }); - after(function () { extension.unregisterExternalExtension(sessionId); }); + after(function () { + extension.unregisterExternalExtension(sessionId); + }); it("Gets non-empty version details from the PowerShell Editor Services", async function () { - const versionDetails: IExternalPowerShellDetails = await extension.getPowerShellVersionDetails(sessionId); + const versionDetails: IExternalPowerShellDetails = + await extension.getPowerShellVersionDetails(sessionId); assert.notStrictEqual(versionDetails.architecture, ""); assert.notStrictEqual(versionDetails.architecture, null); diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index 0f2152c1c6..5a71e7986d 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -9,20 +9,33 @@ import utils = require("../utils"); describe("ISE compatibility feature", function () { let currentTheme: string | undefined; - async function enableISEMode(): Promise { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); } - async function disableISEMode(): Promise { await vscode.commands.executeCommand("PowerShell.DisableISEMode"); } - async function toggleISEMode(): Promise { await vscode.commands.executeCommand("PowerShell.ToggleISEMode"); } + async function enableISEMode(): Promise { + await vscode.commands.executeCommand("PowerShell.EnableISEMode"); + } + async function disableISEMode(): Promise { + await vscode.commands.executeCommand("PowerShell.DisableISEMode"); + } + async function toggleISEMode(): Promise { + await vscode.commands.executeCommand("PowerShell.ToggleISEMode"); + } before(async function () { // Save user's current theme. - currentTheme = await vscode.workspace.getConfiguration("workbench").get("colorTheme"); + currentTheme = await vscode.workspace + .getConfiguration("workbench") + .get("colorTheme"); await utils.ensureEditorServicesIsConnected(); }); after(async function () { // Reset user's current theme. - await vscode.workspace.getConfiguration("workbench").update("colorTheme", currentTheme, true); - assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), currentTheme); + await vscode.workspace + .getConfiguration("workbench") + .update("colorTheme", currentTheme, true); + assert.strictEqual( + vscode.workspace.getConfiguration("workbench").get("colorTheme"), + currentTheme, + ); }); describe("Enable ISE Mode updates expected settings", function () { @@ -30,7 +43,9 @@ describe("ISE compatibility feature", function () { after(disableISEMode); for (const iseSetting of ISECompatibilityFeature.settings) { it(`Sets ${iseSetting.name} correctly`, function () { - const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); + const currently = vscode.workspace + .getConfiguration(iseSetting.path) + .get(iseSetting.name); assert.strictEqual(currently, iseSetting.value); }); } @@ -42,7 +57,9 @@ describe("ISE compatibility feature", function () { after(disableISEMode); for (const iseSetting of ISECompatibilityFeature.settings) { it(`Reverts ${iseSetting.name} correctly`, function () { - const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); + const currently = vscode.workspace + .getConfiguration(iseSetting.path) + .get(iseSetting.name); assert.notStrictEqual(currently, iseSetting.value); }); } @@ -54,7 +71,9 @@ describe("ISE compatibility feature", function () { after(disableISEMode); for (const iseSetting of ISECompatibilityFeature.settings) { it(`Reverts ${iseSetting.name} correctly`, function () { - const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); + const currently = vscode.workspace + .getConfiguration(iseSetting.path) + .get(iseSetting.name); assert.notStrictEqual(currently, iseSetting.value); }); } @@ -66,7 +85,9 @@ describe("ISE compatibility feature", function () { after(disableISEMode); for (const iseSetting of ISECompatibilityFeature.settings) { it(`Sets ${iseSetting.name} correctly`, function () { - const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); + const currently = vscode.workspace + .getConfiguration(iseSetting.path) + .get(iseSetting.name); assert.strictEqual(currently, iseSetting.value); }); } @@ -79,27 +100,53 @@ describe("ISE compatibility feature", function () { function assertISESettings(): void { for (const iseSetting of ISECompatibilityFeature.settings) { - const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); + const currently = vscode.workspace + .getConfiguration(iseSetting.path) + .get(iseSetting.name); assert.notStrictEqual(currently, iseSetting.value); } } it("Changes the theme back from PowerShell ISE", async function () { // Change state to something that DisableISEMode will change - await vscode.workspace.getConfiguration("workbench").update("colorTheme", "PowerShell ISE", true); - assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); + await vscode.workspace + .getConfiguration("workbench") + .update("colorTheme", "PowerShell ISE", true); + assert.strictEqual( + vscode.workspace + .getConfiguration("workbench") + .get("colorTheme"), + "PowerShell ISE", + ); await disableISEMode(); assertISESettings(); }); it("Doesn't change theme if it was manually changed", async function () { - assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); + assert.strictEqual( + vscode.workspace + .getConfiguration("workbench") + .get("colorTheme"), + "PowerShell ISE", + ); // "Manually" change theme after enabling ISE mode. Use a built-in theme but not the default. - await vscode.workspace.getConfiguration("workbench").update("colorTheme", "Monokai", true); - assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Monokai"); + await vscode.workspace + .getConfiguration("workbench") + .update("colorTheme", "Monokai", true); + assert.strictEqual( + vscode.workspace + .getConfiguration("workbench") + .get("colorTheme"), + "Monokai", + ); await disableISEMode(); assertISESettings(); - assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Monokai"); + assert.strictEqual( + vscode.workspace + .getConfiguration("workbench") + .get("colorTheme"), + "Monokai", + ); }); }); }); diff --git a/test/features/UpdatePowerShell.test.ts b/test/features/UpdatePowerShell.test.ts index c4eff1aa11..ef5049b43e 100644 --- a/test/features/UpdatePowerShell.test.ts +++ b/test/features/UpdatePowerShell.test.ts @@ -3,8 +3,8 @@ import assert from "assert"; import { UpdatePowerShell } from "../../src/features/UpdatePowerShell"; -import { Settings } from "../../src/settings"; import type { IPowerShellVersionDetails } from "../../src/session"; +import { Settings } from "../../src/settings"; import { testLogger } from "../utils"; describe("UpdatePowerShell feature", function () { diff --git a/test/utils.ts b/test/utils.ts index ff270fe929..15927ce45a 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { execSync } from "child_process"; import * as path from "path"; import * as vscode from "vscode"; -import type { ILogger } from "../src/logging"; import type { IPowerShellExtensionClient } from "../src/features/ExternalApi"; -import { execSync } from "child_process"; +import type { ILogger } from "../src/logging"; // This lets us test the rest of our path assumptions against the baseline of // this test file existing at `/test/utils.js`. -import { publisher, name } from "../package.json" +import { name, publisher } from "../package.json"; export const extensionId = `${publisher}.${name}`; export class TestLogger implements ILogger { @@ -20,7 +20,10 @@ export class TestLogger implements ILogger { write(_message: string, ..._additionalMessages: string[]): void { return; } - writeAndShowInformation(_message: string, ..._additionalMessages: string[]): Promise { + writeAndShowInformation( + _message: string, + ..._additionalMessages: string[] + ): Promise { return Promise.resolve(); } writeTrace(_message: string, ..._additionalMessages: string[]): void { @@ -32,18 +35,28 @@ export class TestLogger implements ILogger { writeWarning(_message: string, ..._additionalMessages: string[]): void { return; } - writeAndShowWarning(_message: string, ..._additionalMessages: string[]): Promise { + writeAndShowWarning( + _message: string, + ..._additionalMessages: string[] + ): Promise { return Promise.resolve(); } writeError(_message: string, ..._additionalMessages: string[]): void { return; } - writeAndShowError(_message: string, ..._additionalMessages: string[]): Promise { + writeAndShowError( + _message: string, + ..._additionalMessages: string[] + ): Promise { return Promise.resolve(); } writeAndShowErrorWithActions( _message: string, - _actions: { prompt: string; action: (() => Promise) | undefined }[]): Promise { + _actions: { + prompt: string; + action: (() => Promise) | undefined; + }[], + ): Promise { return Promise.resolve(); } } @@ -52,7 +65,9 @@ export const testLogger = new TestLogger(); export async function ensureExtensionIsActivated(): Promise { const extension = vscode.extensions.getExtension(extensionId); - if (!extension!.isActive) { await extension!.activate(); } + if (!extension!.isActive) { + await extension!.activate(); + } return extension!.exports as IPowerShellExtensionClient; } @@ -66,20 +81,24 @@ export async function ensureEditorServicesIsConnected(): Promise(object?: Partial): T { - return object ? object as T : {} as T; + return object ? (object as T) : ({} as T); } /** Builds the sample binary module code. We need to do this because the source maps have absolute paths so they are not portable between machines, and while we could do deterministic with source maps, that's way more complicated and everywhere we build has dotnet already anyways */ export function BuildBinaryModuleMock(): void { - const projectPath = path.resolve(`${__dirname}/../test/mocks/BinaryModule/BinaryModule.csproj`); + const projectPath = path.resolve( + `${__dirname}/../test/mocks/BinaryModule/BinaryModule.csproj`, + ); try { execSync(`dotnet publish ${projectPath}`, { - encoding: "utf8" + encoding: "utf8", }); } catch (err) { - throw new Error(`Failed to build the binary module mock. Please ensure that you have the .NET Core SDK installed: ${err}`); + throw new Error( + `Failed to build the binary module mock. Please ensure that you have the .NET Core SDK installed: ${err}`, + ); } } @@ -87,8 +106,11 @@ export function BuildBinaryModuleMock(): void { * @param event The event to wait for * @param filter An optional filter to apply to the event TResult. The filter will continue to monitor the event firings until the filter returns true. * @returns A promise that resolves when the specified event is fired with the TResult subject of the event. If a filter is specified, the promise will not resolve until the filter returns true. -*/ -export function WaitEvent(event: vscode.Event, filter?: (event: TResult) => boolean | undefined): Promise { + */ +export function WaitEvent( + event: vscode.Event, + filter?: (event: TResult) => boolean | undefined, +): Promise { return new Promise((resolve) => { const listener = event((result: TResult) => { if (!filter || filter(result)) { diff --git a/themes/theme-psise/theme.json b/themes/theme-psise/theme.json index 44474a7289..fcf52d0067 100644 --- a/themes/theme-psise/theme.json +++ b/themes/theme-psise/theme.json @@ -1,211 +1,189 @@ { - "name": "PowerShell ISE", - "semanticHighlighting": true, - "tokenColors": [{ - "settings": { - "background": "#FFFFFF", - "foreground": "#000000" - } - }, - { - "name": "Comments", - "scope": [ - "comment", - "punctuation.definition.comment" - ], - "settings": { - "fontStyle": "italic", - "foreground": "#006400" - } - }, - { - "name": "Comments: Preprocessor", - "scope": "comment.block.preprocessor", - "settings": { - "fontStyle": "", - "foreground": "#006400" - } - }, - { - "name": "Comments: Documentation", - "scope": [ - "comment.documentation", - "comment.block.documentation" - ], - "settings": { - "foreground": "#006400" - } - }, - { - "name": "Invalid - Deprecated", - "scope": "invalid.deprecated", - "settings": { - "background": "#96000014" - } - }, - { - "name": "Invalid - Illegal", - "scope": "invalid.illegal", - "settings": { - "background": "#96000014", - "foreground": "#660000" - } - }, - { - "name": "Operators", - "scope": "keyword.operator", - "settings": { - "foreground": "#A9A9A9" - } - }, - { - "name": "Keywords", - "scope": [ - "keyword", - "storage" - ], - "settings": { - "foreground": "#00008B" - } - }, - { - "name": "Types", - "scope": [ - "storage.type", - "support.type" - ], - "settings": { - "foreground": "#00008B" - } - }, - { - "name": "Language Constants", - "scope": [ - "constant.language", - "support.constant", - "variable.language" - ], - "settings": { - "foreground": "#008080" - } - }, - { - "name": "Keys and Properties", - "scope": [ - "property", - "variable.other.property", - "variable.other.property.powershell" - ], - "settings": { - "foreground": "#2d2e45" - } - }, - { - "name": "Variables", - "scope": [ - "variable", - "support.variable", - "punctuation.definition.variable.powershell", - "variable.other.readwrite.powershell" - ], - "settings": { - "foreground": "#FF4500" - } - }, - { - "name": "Functions", - "scope": [ - "entity.name.function", - "support.function" - ], - "settings": { - "foreground": "#0000FF" - } - }, - { - "name": "Classes", - "scope": [ - "entity.name.type", - "entity.other.inherited-class", - "support.class" - ], - "settings": { - "foreground": "#7A3E9D" - } - }, - { - "name": "Exceptions", - "scope": "entity.name.exception", - "settings": { - "foreground": "#660000" - } - }, - { - "name": "Sections", - "scope": "entity.name.section", - "settings": {} - }, - { - "name": "Numbers, Characters", - "scope": [ - "constant.numeric", - "constant.character", - "constant" - ], - "settings": { - "foreground": "#800080" - } - }, - { - "name": "Strings", - "scope": "string", - "settings": { - "foreground": "#8B0000" - } - }, - { - "name": "Strings: Escape Sequences", - "scope": "constant.character.escape", - "settings": { - "foreground": "#8B0000" - } - }, - { - "name": "Strings: Regular Expressions", - "scope": "string.regexp", - "settings": { - "foreground": "#8B0000" - } - }, - { - "name": "Strings: Symbols", - "scope": "constant.other.symbol", - "settings": { - "foreground": "#8B0000" - } - }, - { - "name": "Punctuation", - "scope": "punctuation", - "settings": { - "foreground": "#000000" - } - } - ], - "colors": { - "activityBar.background": "#E1ECF9", - "activityBar.foreground": "#A9A9A9", - "activityBarBadge.background": "#A9A9A9", - "editor.lineHighlightBackground": "#add8e6", - "editor.selectionBackground": "#94c6f7", - "settings.checkboxBorder": "#A9A9A9", - "settings.dropdownBorder": "#A9A9A9", - "settings.numberInputBorder": "#A9A9A9", - "settings.textInputBorder": "#A9A9A9", - "statusBar.background": "#999999", - "statusBar.debuggingBackground": "#FF4500", - "statusBar.noFolderBackground": "#999999", - "terminal.background": "#012456", - "terminal.foreground": "#F5F5F5" + "name": "PowerShell ISE", + "semanticHighlighting": true, + "tokenColors": [ + { + "settings": { + "background": "#FFFFFF", + "foreground": "#000000" + } + }, + { + "name": "Comments", + "scope": ["comment", "punctuation.definition.comment"], + "settings": { + "fontStyle": "italic", + "foreground": "#006400" + } + }, + { + "name": "Comments: Preprocessor", + "scope": "comment.block.preprocessor", + "settings": { + "fontStyle": "", + "foreground": "#006400" + } + }, + { + "name": "Comments: Documentation", + "scope": ["comment.documentation", "comment.block.documentation"], + "settings": { + "foreground": "#006400" + } + }, + { + "name": "Invalid - Deprecated", + "scope": "invalid.deprecated", + "settings": { + "background": "#96000014" + } + }, + { + "name": "Invalid - Illegal", + "scope": "invalid.illegal", + "settings": { + "background": "#96000014", + "foreground": "#660000" + } + }, + { + "name": "Operators", + "scope": "keyword.operator", + "settings": { + "foreground": "#A9A9A9" + } + }, + { + "name": "Keywords", + "scope": ["keyword", "storage"], + "settings": { + "foreground": "#00008B" + } + }, + { + "name": "Types", + "scope": ["storage.type", "support.type"], + "settings": { + "foreground": "#00008B" + } + }, + { + "name": "Language Constants", + "scope": ["constant.language", "support.constant", "variable.language"], + "settings": { + "foreground": "#008080" + } + }, + { + "name": "Keys and Properties", + "scope": [ + "property", + "variable.other.property", + "variable.other.property.powershell" + ], + "settings": { + "foreground": "#2d2e45" + } + }, + { + "name": "Variables", + "scope": [ + "variable", + "support.variable", + "punctuation.definition.variable.powershell", + "variable.other.readwrite.powershell" + ], + "settings": { + "foreground": "#FF4500" + } + }, + { + "name": "Functions", + "scope": ["entity.name.function", "support.function"], + "settings": { + "foreground": "#0000FF" + } + }, + { + "name": "Classes", + "scope": [ + "entity.name.type", + "entity.other.inherited-class", + "support.class" + ], + "settings": { + "foreground": "#7A3E9D" + } + }, + { + "name": "Exceptions", + "scope": "entity.name.exception", + "settings": { + "foreground": "#660000" + } + }, + { + "name": "Sections", + "scope": "entity.name.section", + "settings": {} + }, + { + "name": "Numbers, Characters", + "scope": ["constant.numeric", "constant.character", "constant"], + "settings": { + "foreground": "#800080" + } + }, + { + "name": "Strings", + "scope": "string", + "settings": { + "foreground": "#8B0000" + } + }, + { + "name": "Strings: Escape Sequences", + "scope": "constant.character.escape", + "settings": { + "foreground": "#8B0000" + } + }, + { + "name": "Strings: Regular Expressions", + "scope": "string.regexp", + "settings": { + "foreground": "#8B0000" + } + }, + { + "name": "Strings: Symbols", + "scope": "constant.other.symbol", + "settings": { + "foreground": "#8B0000" + } + }, + { + "name": "Punctuation", + "scope": "punctuation", + "settings": { + "foreground": "#000000" + } } -} \ No newline at end of file + ], + "colors": { + "activityBar.background": "#E1ECF9", + "activityBar.foreground": "#A9A9A9", + "activityBarBadge.background": "#A9A9A9", + "editor.lineHighlightBackground": "#add8e6", + "editor.selectionBackground": "#94c6f7", + "settings.checkboxBorder": "#A9A9A9", + "settings.dropdownBorder": "#A9A9A9", + "settings.numberInputBorder": "#A9A9A9", + "settings.textInputBorder": "#A9A9A9", + "statusBar.background": "#999999", + "statusBar.debuggingBackground": "#FF4500", + "statusBar.noFolderBackground": "#999999", + "terminal.background": "#012456", + "terminal.foreground": "#F5F5F5" + } +} diff --git a/vscode-powershell.build.ps1 b/vscode-powershell.build.ps1 index 7006cd1a73..4bcc737e33 100644 --- a/vscode-powershell.build.ps1 +++ b/vscode-powershell.build.ps1 @@ -86,6 +86,8 @@ task CleanEditorServices -If (Get-EditorServicesPath) { task Lint RestoreNodeOptional, { Write-Build DarkMagenta "Linting TypeScript" Invoke-BuildExec { & npm run lint } + Write-Build DarkMagenta "Checking formatting of TypeScript, JSON, etc." + Invoke-BuildExec { & npm run format } } task Build RestoreEditorServices, RestoreNode, {