src/vs/workbench/api/common/extHostTerminalService.ts

1338 LOC · 571 covered · 767 uncovered · 140 ranges · 47 concepts · 2 introducers · 42 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/base/common/uri.ts · 754 LOCcommon/uri.tssrc/vs/workbench/api/common/extHostCommands.ts · 507 LOCcommon/extHostCommands.t…src/vs/workbench/api/common/extHostTelemetry.ts · 343 LOCcommon/extHostTelemetry.…src/vs/workbench/api/common/extHostTestItem.ts · 197 LOCcommon/extHostTestItem.t…src/vs/workbench/api/common/extHostTypes.ts · 4312 LOCcommon/extHostTypes.tssrc/vs/workbench/services/extensions/common/extensionHostProtocol.ts · 163 LOCcommon/extensionHostProt…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 3 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 4 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×2 · 4 introduced LOCextHostMcp.ts ×2extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×4 · 6 introduced LOCextHostMcp.ts ×4extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×4 · 13 introduced LOCextHostMcp.ts ×4extHostMcpNode.ts ×8 · 45 introduced LOCextHostMcpNode.ts ×8extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×5 · 25 introduced LOCextHostMcp.ts ×5extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×3 · 23 introduced LOCextHostMcp.ts ×3extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×8 · 60 introduced LOCextHostMcp.ts ×8extHostMcp.ts ×43 · 874 introduced LOCextHostMcp.ts ×43extensionHostMain.ts ×1 · 2 introduced LOCextensionHostMain.ts ×1reset prepareStackTrace-callback|occurrence=1, extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 prevent rewrapping|occurrence=1 · 0 introduced LOCreset prepareStackTrace-…extensionHostMain.ts ×5 · 26 introduced LOCextensionHostMain.ts ×5f0f2e182082072efdaf0f8e1537d2cce Restored, too many uses before restoration|occurrence=1 · 0 introduced LOCf0f2e182082072efdaf0f8e1…extensionHostMain.ts ×1 · 3 introduced LOCextensionHostMain.ts ×1extensionHostMain.ts ×1 · 3 introduced LOCextensionHostMain.ts ×1extensionHostMain.ts ×11 · 164 introduced LOCextensionHostMain.ts ×11extHostExtensionService.ts ×64 · 1246 introduced LOCextHostExtensionService.…extHostTerminalService.ts ×31 · 156 introduced LOCextHostTerminalService.t…extHostTerminalShellIntegration.ts ×3 · 13 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×6 · 28 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×13 · 51 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×1 · 4 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×1 · 3 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×1 · 2 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×29 · 253 introduced LOCextHostTerminalShellInte…extHostTerminalService.ts ×109 · 894 introduced LOCextHostTerminalService.t…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1extHostMcp.test|title=Ex…extHostTerminalService.test|title=ExtHostTerminalService $acceptTerminalClosed cancels in-flight link providers and clears the link cache|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalService.test|title=ExtHostTerminalService $acceptTerminalClosed cancels in-flight link providers and clears the link cache|occurrence=1extHostTerminalService.t…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration different execution unexpectedly ended|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration different execution unexpectedly ended|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand 4 multi-line commands with output|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand 4 multi-line commands with output|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand ^C to clear previous command|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand ^C to clear previous command|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command comment followed by long second command|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command comment followed by long second command|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command line|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command line|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command with long second command|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command with long second command|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration no end event|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration no end event|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration simple execution|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration simple execution|occurrence=1extHostTerminalShellInte…extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 basics|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 basics|occurrence=1extensionHostMain.test|t…f0f2e182082072efdaf0f8e1537d2cce Never restored, separate operations|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce Never restored, separate operations|occurrence=1f0f2e182082072efdaf0f8e1…f0f2e182082072efdaf0f8e1537d2cce Restored, separate operations|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce Restored, separate operations|occurrence=1f0f2e182082072efdaf0f8e1…f0f2e182082072efdaf0f8e1537d2cce Restored, too many uses before restoration|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce Restored, too many uses before restoration|occurrence=1f0f2e182082072efdaf0f8e1…extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 prevent rewrapping|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 prevent rewrapping|occurrence=1extensionHostMain.test|t…reset prepareStackTrace-callback|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 set/reset prepareStackTrace-callback|occurrence=1reset prepareStackTrace-…extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 wrap prepareStackTrace-callback|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 wrap prepareStackTrace-callback|occurrence=1extensionHostMain.test|t…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1extHostMcpNode.test|titl…Focused file · src/vs/workbench/api/common/extHostTerminalService.ts · 1338 LOCcommon/extHostTerminalSe…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extHostTerminalService.ts ×109
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import type * as vscode from 'vscode';
7 > import { Event, Emitter } from '../../../base/common/event.js';
8 > import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, ITerminalDimensionsDto, ITerminalLinkDto, ExtHostTerminalIdentifier, ICommandDto, ITerminalQuickFixOpenerDto, ITerminalQuickFixTerminalCommandDto, TerminalCommandMatchResultDto, ITerminalCommandDto, ITerminalCompletionContextDto, TerminalCompletionListDto } from './extHost.protocol.js';
9 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
10 > import { URI } from '../../../base/common/uri.js';
11 > import { IExtHostRpcService } from './extHostRpcService.js';
12 > import { IDisposable, DisposableStore, Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
13 > import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType, TerminalExitReason, TerminalCompletionItem } from './extHostTypes.js';
14 > import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
15 > import { localize } from '../../../nls.js';
16 > import { NotSupportedError } from '../../../base/common/errors.js';
17 > import { serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollection } from '../../../platform/terminal/common/environmentVariableShared.js';
18 > import { CancellationTokenSource } from '../../../base/common/cancellation.js';
19 > import { generateUuid } from '../../../base/common/uuid.js';
20 > import { IEnvironmentVariableCollectionDescription, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from '../../../platform/terminal/common/environmentVariable.js';
21 > import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, TerminalShellType, WindowsShellType } from '../../../platform/terminal/common/terminal.js';
22 > import { TerminalDataBufferer } from '../../../platform/terminal/common/terminalDataBuffering.js';
23 > import { ThemeColor } from '../../../base/common/themables.js';
24 > import { Promises } from '../../../base/common/async.js';
25 > import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js';
26 > import { TerminalCompletionList, TerminalQuickFix, ViewColumn } from './extHostTypeConverters.js';
27 > import { IExtHostCommands } from './extHostCommands.js';
28 > import { IExtHostInitDataService } from './extHostInitDataService.js';
29 > import { MarshalledId } from '../../../base/common/marshallingIds.js';
30 > import { ISerializedTerminalInstanceContext } from '../../contrib/terminal/common/terminal.js';
31 > import { isWindows } from '../../../base/common/platform.js';
32 > import { hasKey } from '../../../base/common/types.js';
33 > import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
34 >
35 > export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
36 >
37 > readonly _serviceBrand: undefined;
38 >
39 > activeTerminal: vscode.Terminal | undefined;
40 > terminals: vscode.Terminal[];
41 >
42 > readonly onDidCloseTerminal: Event<vscode.Terminal>;
43 > readonly onDidOpenTerminal: Event<vscode.Terminal>;
44 > readonly onDidChangeActiveTerminal: Event<vscode.Terminal | undefined>;
45 > readonly onDidChangeTerminalDimensions: Event<vscode.TerminalDimensionsChangeEvent>;
46 > readonly onDidChangeTerminalState: Event<vscode.Terminal>;
47 > readonly onDidWriteTerminalData: Event<vscode.TerminalDataWriteEvent>;
48 > readonly onDidExecuteTerminalCommand: Event<vscode.TerminalExecutedCommand>;
49 > readonly onDidChangeShell: Event<string>;
50 >
51 > createTerminal(name?: string, shellPath?: string, shellArgs?: readonly string[] | string): vscode.Terminal;
52 > createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal;
53 > createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal;
54 > attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void;
55 > getDefaultShell(useAutomationShell: boolean): string;
56 > getDefaultShellArgs(useAutomationShell: boolean): string[] | string;
57 > registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable;
58 > registerProfileProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable;
59 > registerTerminalQuickFixProvider(id: string, extensionId: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable;
60 > getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection;
61 > getTerminalById(id: number): ExtHostTerminal | null;
62 > getTerminalIdByApiObject(apiTerminal: vscode.Terminal): number | null;
63 > registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable;
64 > }
65 >
66 > interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableCollection {
67 > getScoped(scope: vscode.EnvironmentVariableScope): vscode.EnvironmentVariableCollection;
68 > }
69 >
70 > export interface ITerminalInternalOptions {
71 > cwd?: string | URI;
72 > isFeatureTerminal?: boolean;
73 > forceShellIntegration?: boolean;
74 > useShellEnvironment?: boolean;
75 > resolvedExtHostIdentifier?: ExtHostTerminalIdentifier;
76 > /**
77 > * This location is different from the API location because it can include splitActiveTerminal,
78 > * a property we resolve internally
79 > */
80 > location?: TerminalLocation | { viewColumn: number; preserveState?: boolean } | { splitActiveTerminal: boolean };
81 > }
82 >
83 > export const IExtHostTerminalService = createDecorator<IExtHostTerminalService>('IExtHostTerminalService');
84 >
85 > export class ExtHostTerminal extends Disposable {
86 > private _disposed: boolean = false;
87 > private _pidPromise: Promise<number | undefined>;
88 > private _cols: number | undefined;
89 > private _pidPromiseComplete: ((value: number | undefined) => unknown) | undefined;
90 > private _rows: number | undefined;
91 > private _exitStatus: vscode.TerminalExitStatus | undefined;
92 > private _state: vscode.TerminalState = { isInteractedWith: false, shell: undefined };
93 > private _selection: string | undefined;
94 >
95 > shellIntegration: vscode.TerminalShellIntegration | undefined;
96 >
97 > public isOpen: boolean = false;
98 >
99 > readonly value: vscode.Terminal;
100 >
101 > protected readonly _onWillDispose = this._register(new Emitter<void>());
102 > readonly onWillDispose = this._onWillDispose.event;
103 >
104 > constructor(
105 > private _proxy: MainThreadTerminalServiceShape, extHostTerminalService.ts ×31
106 > public _id: ExtHostTerminalIdentifier,
107 > private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions,
108 > private _name?: string,
109 > ) {
110 > super();
111 >
112 > this._creationOptions = Object.freeze(this._creationOptions);
113 > this._pidPromise = new Promise<number | undefined>(c => this._pidPromiseComplete = c);
114 >
115 > const that = this;
116 > this.value = {
117 > get name(): string {
118 return that._name || '';
119 },
120 > get processId(): Promise<number | undefined> { extHostTerminalService.ts ×31
121 return that._pidPromise;
122 },
123 > get creationOptions(): Readonly<vscode.TerminalOptions | vscode.ExtensionTerminalOptions> { extHostTerminalService.ts ×31
124 return that._creationOptions;
125 },
126 > get exitStatus(): vscode.TerminalExitStatus | undefined { extHostTerminalService.ts ×31
127 return that._exitStatus;
128 },
129 > get state(): vscode.TerminalState { extHostTerminalService.ts ×31
130 return that._state;
131 },
132 > get selection(): string | undefined { extHostTerminalService.ts ×31
133 return that._selection;
134 },
135 > get shellIntegration(): vscode.TerminalShellIntegration | undefined { extHostTerminalService.ts ×31
136 return that.shellIntegration;
137 },
138 > sendText(text: string, shouldExecute: boolean = true): void { extHostTerminalService.ts ×31
139 that._checkDisposed();
140 that._proxy.$sendText(that._id, text, shouldExecute);
141 },
142 > show(preserveFocus: boolean): void { extHostTerminalService.ts ×31
143 that._checkDisposed();
144 that._proxy.$show(that._id, preserveFocus);
145 },
146 > hide(): void { extHostTerminalService.ts ×31
147 that._checkDisposed();
148 that._proxy.$hide(that._id);
149 },
150 > dispose(): void { extHostTerminalService.ts ×31
151 if (!that._disposed) {
152 that._disposed = true;
153 that._proxy.$dispose(that._id);
154 }
155 },
156 > get dimensions(): vscode.TerminalDimensions | undefined { extHostTerminalService.ts ×31
157 if (that._cols === undefined || that._rows === undefined) {
158 return undefined;
159 }
160 return {
161 columns: that._cols,
162 rows: that._rows
163 };
164 }
166 > }
168 > override dispose(): void {
169 > this._onWillDispose.fire(); extHostTerminalService.ts ×31
170 > super.dispose();
171 > }
173 > public async create(
174 options: vscode.TerminalOptions,
175 internalOptions?: ITerminalInternalOptions,
176 ): Promise<void> {
177 if (typeof this._id !== 'string') {
178 throw new Error('Terminal has already been created');
179 }
180 await this._proxy.$createTerminal(this._id, {
181 name: options.name,
182 shellPath: options.shellPath ?? undefined,
183 shellArgs: options.shellArgs ?? undefined,
184 cwd: options.cwd ?? internalOptions?.cwd ?? undefined,
185 env: options.env ?? undefined,
186 icon: asTerminalIcon(options.iconPath) ?? undefined,
187 color: ThemeColor.isThemeColor(options.color) ? options.color.id : undefined,
188 initialText: options.message ?? undefined,
189 strictEnv: options.strictEnv ?? undefined,
190 hideFromUser: options.hideFromUser ?? undefined,
191 forceShellIntegration: internalOptions?.forceShellIntegration ?? undefined,
192 isFeatureTerminal: internalOptions?.isFeatureTerminal ?? undefined,
193 isExtensionOwnedTerminal: true,
194 useShellEnvironment: internalOptions?.useShellEnvironment ?? undefined,
195 location: internalOptions?.location || this._serializeParentTerminal(options.location, internalOptions?.resolvedExtHostIdentifier),
196 isTransient: options.isTransient ?? undefined,
197 shellIntegrationNonce: options.shellIntegrationNonce ?? undefined,
198 titleTemplate: options.titleTemplate ?? undefined,
199 });
200 }
202 >
203 > public async createExtensionTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, internalOptions?: ITerminalInternalOptions, parentTerminal?: ExtHostTerminalIdentifier, iconPath?: TerminalIcon, color?: ThemeColor, shellIntegrationNonce?: string, titleTemplate?: string): Promise<number> {
204 if (typeof this._id !== 'string') {
205 throw new Error('Terminal has already been created');
206 }
207 await this._proxy.$createTerminal(this._id, {
208 name: this._name,
209 isExtensionCustomPtyTerminal: true,
210 icon: iconPath,
211 color: ThemeColor.isThemeColor(color) ? color.id : undefined,
212 location: internalOptions?.location || this._serializeParentTerminal(location, parentTerminal),
213 isTransient: true,
214 shellIntegrationNonce: shellIntegrationNonce ?? undefined,
215 titleTemplate: titleTemplate ?? undefined,
216 });
217 // At this point, the id has been set via `$acceptTerminalOpened`
218 if (typeof this._id === 'string') {
219 throw new Error('Terminal creation failed');
220 }
221 return this._id;
222 }
224 > private _serializeParentTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, parentTerminal?: ExtHostTerminalIdentifier): TerminalLocation | { viewColumn: EditorGroupColumn; preserveFocus?: boolean } | { parentTerminal: ExtHostTerminalIdentifier } | undefined {
225 if (typeof location === 'object') {
226 if (hasKey(location, { parentTerminal: true }) && location.parentTerminal && parentTerminal) {
227 return { parentTerminal };
228 }
229
230 if (hasKey(location, { viewColumn: true })) {
231 return { viewColumn: ViewColumn.from(location.viewColumn), preserveFocus: location.preserveFocus };
232 }
233
234 return undefined;
235 }
236
237 return location;
238 }
240 > private _checkDisposed() {
241 if (this._disposed) {
242 throw new Error('Terminal has already been disposed');
243 }
244 }
246 > public set name(name: string) {
247 this._name = name;
248 }
250 > public setExitStatus(code: number | undefined, reason: TerminalExitReason) {
251 > this._exitStatus = Object.freeze({ code, reason }); extHostTerminalService.ts ×31
252 > }
254 > public setDimensions(cols: number, rows: number): boolean {
255 if (cols === this._cols && rows === this._rows) {
256 // Nothing changed
257 return false;
258 }
259 if (cols === 0 || rows === 0) {
260 return false;
261 }
262 this._cols = cols;
263 this._rows = rows;
264 return true;
265 }
267 > public setInteractedWith(): boolean {
268 if (!this._state.isInteractedWith) {
269 this._state = {
270 ...this._state,
271 isInteractedWith: true
272 };
273 return true;
274 }
275 return false;
276 }
278 > public setShellType(shellType: TerminalShellType | undefined): boolean {
279
280 if (this._state.shell !== shellType) {
281 this._state = {
282 ...this._state,
283 shell: shellType
284 };
285 return true;
286 }
287 return false;
288 }
290 > public setSelection(selection: string | undefined): void {
291 this._selection = selection;
292 }
294 > public _setProcessId(processId: number | undefined): void {
295 // The event may fire 2 times when the panel is restored
296 if (this._pidPromiseComplete) {
297 this._pidPromiseComplete(processId);
298 this._pidPromiseComplete = undefined;
299 } else {
300 // Recreate the promise if this is the nth processId set (e.g. reused task terminals)
301 this._pidPromise.then(pid => {
302 if (pid !== processId) {
303 this._pidPromise = Promise.resolve(processId);
304 }
305 });
306 }
307 }
309 >
310 > class ExtHostPseudoterminal implements ITerminalChildProcess {
311 > readonly id = 0;
312 > readonly shouldPersist = false;
313 >
314 > private readonly _onProcessData = new Emitter<string>();
315 > public readonly onProcessData: Event<string> = this._onProcessData.event;
316 > private readonly _onProcessReady = new Emitter<IProcessReadyEvent>();
317 > public get onProcessReady(): Event<IProcessReadyEvent> { return this._onProcessReady.event; }
318 > private readonly _onDidChangeProperty = new Emitter<IProcessProperty>();
319 > public readonly onDidChangeProperty = this._onDidChangeProperty.event;
320 > private readonly _onProcessExit = new Emitter<number | undefined>();
321 > public readonly onProcessExit: Event<number | undefined> = this._onProcessExit.event;
322 >
323 > constructor(private readonly _pty: vscode.Pseudoterminal) { }
324 >
325 > refreshProperty<T extends ProcessPropertyType>(property: ProcessPropertyType): Promise<IProcessPropertyMap[T]> {
326 throw new Error(`refreshProperty is not suppported in extension owned terminals. property: ${property}`);
327 }
329 > updateProperty<T extends ProcessPropertyType>(property: ProcessPropertyType, value: IProcessPropertyMap[T]): Promise<void> {
330 throw new Error(`updateProperty is not suppported in extension owned terminals. property: ${property}, value: ${value}`);
331 }
333 > async start(): Promise<undefined> {
334 return undefined;
335 }
337 > shutdown(): void {
338 this._pty.close();
339 }
341 > input(data: string): void {
342 this._pty.handleInput?.(data);
343 }
345 > sendSignal(signal: string): void {
346 // Extension owned terminals don't support sending signals directly to processes
347 // This could be extended in the future if the pseudoterminal API is enhanced
348 }
350 > resize(cols: number, rows: number): void {
351 this._pty.setDimensions?.({ columns: cols, rows });
352 }
354 > clearBuffer(): void {
355 // no-op
356 }
358 > async processBinary(data: string): Promise<void> {
359 // No-op, processBinary is not supported in extension owned terminals.
360 }
362 > acknowledgeDataEvent(charCount: number): void {
363 // No-op, flow control is not supported in extension owned terminals. If this is ever
364 // implemented it will need new pause and resume VS Code APIs.
365 }
367 > async setUnicodeVersion(version: '6' | '11'): Promise<void> {
368 // No-op, xterm-headless isn't used for extension owned terminals.
369 }
371 > getInitialCwd(): Promise<string> {
372 return Promise.resolve('');
373 }
375 > getCwd(): Promise<string> {
376 return Promise.resolve('');
377 }
379 > startSendingEvents(initialDimensions: ITerminalDimensionsDto | undefined): void {
380 // Attach the listeners
381 this._pty.onDidWrite(e => this._onProcessData.fire(e));
382 this._pty.onDidClose?.((e: number | void = undefined) => {
383 this._onProcessExit.fire(e === void 0 ? undefined : e);
384 });
385 this._pty.onDidOverrideDimensions?.(e => {
386 if (e) {
387 this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: e.columns, rows: e.rows } });
388 }
389 });
390 this._pty.onDidChangeName?.(title => {
391 this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: title });
392 });
393
394 this._pty.open(initialDimensions ? initialDimensions : undefined);
395
396 if (initialDimensions) {
397 this._pty.setDimensions?.(initialDimensions);
398 }
399
400 this._onProcessReady.fire({ pid: -1, cwd: '', windowsPty: undefined });
401 }
403 >
404 > let nextLinkId = 1;
405 >
406 > interface ICachedLinkEntry {
407 > provider: vscode.TerminalLinkProvider;
408 > link: vscode.TerminalLink;
409 > }
410 >
411 > export abstract class BaseExtHostTerminalService extends Disposable implements IExtHostTerminalService, ExtHostTerminalServiceShape {
412 >
413 > readonly _serviceBrand: undefined;
414 >
415 > protected _proxy: MainThreadTerminalServiceShape;
416 > protected _activeTerminal: ExtHostTerminal | undefined;
417 > protected _terminals: ExtHostTerminal[] = [];
418 > protected _terminalProcesses: Map<number, ITerminalChildProcess> = new Map();
419 > protected _terminalProcessDisposables: { [id: number]: IDisposable } = {};
420 > protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {};
421 > protected _getTerminalPromises: { [id: number]: Promise<ExtHostTerminal | undefined> } = {};
422 > protected _environmentVariableCollections: Map<string, UnifiedEnvironmentVariableCollection> = new Map();
423 > private _defaultProfile: ITerminalProfile | undefined;
424 > private _defaultAutomationProfile: ITerminalProfile | undefined;
425 > private readonly _lastQuickFixCommands: MutableDisposable<IDisposable> = this._register(new MutableDisposable());
426 >
427 > private readonly _bufferer: TerminalDataBufferer;
428 > private readonly _linkProviders: Set<vscode.TerminalLinkProvider> = new Set();
429 > private readonly _completionProviders: Map<string, vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>> = new Map();
430 > private readonly _profileProviders: Map<string, { provider: vscode.TerminalProfileProvider; extension: IExtensionDescription }> = new Map();
431 > private readonly _quickFixProviders: Map<string, vscode.TerminalQuickFixProvider> = new Map();
432 > private readonly _terminalLinkCache: Map<number, Map<number, ICachedLinkEntry>> = new Map();
433 > private readonly _terminalLinkCancellationSource: Map<number, CancellationTokenSource> = new Map();
434 >
435 > public get activeTerminal(): vscode.Terminal | undefined { return this._activeTerminal?.value; }
436 > public get terminals(): vscode.Terminal[] { return this._terminals.map(term => term.value); }
437 >
438 > protected readonly _onDidCloseTerminal = new Emitter<vscode.Terminal>();
439 > readonly onDidCloseTerminal = this._onDidCloseTerminal.event;
440 > protected readonly _onDidOpenTerminal = new Emitter<vscode.Terminal>();
441 > readonly onDidOpenTerminal = this._onDidOpenTerminal.event;
442 > protected readonly _onDidChangeActiveTerminal = new Emitter<vscode.Terminal | undefined>();
443 > readonly onDidChangeActiveTerminal = this._onDidChangeActiveTerminal.event;
444 > protected readonly _onDidChangeTerminalDimensions = new Emitter<vscode.TerminalDimensionsChangeEvent>();
445 > readonly onDidChangeTerminalDimensions = this._onDidChangeTerminalDimensions.event;
446 > protected readonly _onDidChangeTerminalState = new Emitter<vscode.Terminal>();
447 > readonly onDidChangeTerminalState = this._onDidChangeTerminalState.event;
448 > protected readonly _onDidChangeShell = new Emitter<string>();
449 > readonly onDidChangeShell = this._onDidChangeShell.event;
450 >
451 > protected readonly _onDidWriteTerminalData = new Emitter<vscode.TerminalDataWriteEvent>({
452 > onWillAddFirstListener: () => this._proxy.$startSendingDataEvents(),
453 > onDidRemoveLastListener: () => this._proxy.$stopSendingDataEvents()
454 > });
455 > readonly onDidWriteTerminalData = this._onDidWriteTerminalData.event;
456 > protected readonly _onDidExecuteCommand = new Emitter<vscode.TerminalExecutedCommand>({
457 > onWillAddFirstListener: () => this._proxy.$startSendingCommandEvents(),
458 > onDidRemoveLastListener: () => this._proxy.$stopSendingCommandEvents()
459 > });
460 > readonly onDidExecuteTerminalCommand = this._onDidExecuteCommand.event;
461 >
462 > constructor(
463 > supportsProcesses: boolean, extHostTerminalService.ts ×31
464 > @IExtHostCommands private readonly _extHostCommands: IExtHostCommands,
465 > @IExtHostRpcService extHostRpc: IExtHostRpcService
466 > ) {
467 > super();
468 > this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService);
469 > this._bufferer = new TerminalDataBufferer(this._proxy.$sendProcessData);
470 > this._proxy.$registerProcessSupport(supportsProcesses);
471 > this._extHostCommands.registerArgumentProcessor({
472 > processArgument: arg => {
473 const deserialize = (arg: ISerializedTerminalInstanceContext) => {
474 return this.getTerminalById(arg.instanceId)?.value;
475 };
476 switch (arg?.$mid) {
477 case MarshalledId.TerminalContext: return deserialize(arg);
478 default: {
479 // Do array transformation in place as this is a hot path
480 if (Array.isArray(arg)) {
481 for (let i = 0; i < arg.length; i++) {
482 if (arg[i].$mid === MarshalledId.TerminalContext) {
483 arg[i] = deserialize(arg[i]);
484 } else {
485 // Probably something else, so exit early
486 break;
487 }
488 }
489 }
490 return arg;
491 }
492 }
493 }
495 > this._register({
496 > dispose: () => {
497 > for (const [_, terminalProcess] of this._terminalProcesses) {
498 terminalProcess.shutdown(true);
499 }
501 > });
502 > }
504 > public abstract createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal;
505 > public abstract createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal;
506 >
507 > public getDefaultShell(useAutomationShell: boolean): string {
508 const profile = useAutomationShell ? this._defaultAutomationProfile : this._defaultProfile;
509 return profile?.path || '';
510 }
512 > public getDefaultShellArgs(useAutomationShell: boolean): string[] | string {
513 const profile = useAutomationShell ? this._defaultAutomationProfile : this._defaultProfile;
514 return profile?.args || [];
515 }
517 > public createExtensionTerminal(options: vscode.ExtensionTerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal {
518 const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
519 const p = new ExtHostPseudoterminal(options.pty);
520 terminal.createExtensionTerminal(options.location, internalOptions, this._serializeParentTerminal(options, internalOptions).resolvedExtHostIdentifier, asTerminalIcon(options.iconPath), asTerminalColor(options.color), options.shellIntegrationNonce, options.titleTemplate).then(id => {
521 const disposable = this._setupExtHostProcessListeners(id, p);
522 this._terminalProcessDisposables[id] = disposable;
523 });
524 this._terminals.push(terminal);
525 return terminal.value;
526 }
528 > protected _serializeParentTerminal(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): ITerminalInternalOptions {
529 internalOptions = internalOptions ? internalOptions : {};
530 if (options.location && typeof options.location === 'object' && hasKey(options.location, { parentTerminal: true })) {
531 const parentTerminal = options.location.parentTerminal;
532 if (parentTerminal) {
533 const parentExtHostTerminal = this._terminals.find(t => t.value === parentTerminal);
534 if (parentExtHostTerminal) {
535 internalOptions.resolvedExtHostIdentifier = parentExtHostTerminal._id;
536 }
537 }
538 } else if (options.location && typeof options.location !== 'object') {
539 internalOptions.location = options.location;
540 } else if (internalOptions.location && typeof internalOptions.location === 'object' && hasKey(internalOptions.location, { splitActiveTerminal: true })) {
541 internalOptions.location = { splitActiveTerminal: true };
542 }
543 return internalOptions;
544 }
546 > public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void {
547 const terminal = this.getTerminalById(id);
548 if (!terminal) {
549 throw new Error(`Cannot resolve terminal with id ${id} for virtual process`);
550 }
551 const p = new ExtHostPseudoterminal(pty);
552 const disposable = this._setupExtHostProcessListeners(id, p);
553 this._terminalProcessDisposables[id] = disposable;
554 }
556 > public async $acceptActiveTerminalChanged(id: number | null): Promise<void> {
557 const original = this._activeTerminal;
558 if (id === null) {
559 this._activeTerminal = undefined;
560 if (original !== this._activeTerminal) {
561 this._onDidChangeActiveTerminal.fire(this._activeTerminal);
562 }
563 return;
564 }
565 const terminal = this.getTerminalById(id);
566 if (terminal) {
567 this._activeTerminal = terminal;
568 if (original !== this._activeTerminal) {
569 this._onDidChangeActiveTerminal.fire(this._activeTerminal.value);
570 }
571 }
572 }
574 > public async $acceptTerminalProcessData(id: number, data: string): Promise<void> {
575 const terminal = this.getTerminalById(id);
576 if (terminal) {
577 this._onDidWriteTerminalData.fire({ terminal: terminal.value, data });
578 }
579 }
581 > public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise<void> {
582 const terminal = this.getTerminalById(id);
583 if (terminal) {
584 if (terminal.setDimensions(cols, rows)) {
585 this._onDidChangeTerminalDimensions.fire({
586 terminal: terminal.value,
587 dimensions: terminal.value.dimensions as vscode.TerminalDimensions
588 });
589 }
590 }
591 }
593 > public async $acceptDidExecuteCommand(id: number, command: ITerminalCommandDto): Promise<void> {
594 const terminal = this.getTerminalById(id);
595 if (terminal) {
596 this._onDidExecuteCommand.fire({ terminal: terminal.value, ...command });
597 }
598 }
600 > public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise<void> {
601 // Extension pty terminal only - when virtual process resize fires it means that the
602 // terminal's maximum dimensions changed
603 this._terminalProcesses.get(id)?.resize(cols, rows);
604 }
606 > public async $acceptTerminalTitleChange(id: number, name: string): Promise<void> {
607 const terminal = this.getTerminalById(id);
608 if (terminal) {
609 terminal.name = name;
610 }
611 }
613 > public async $acceptTerminalClosed(id: number, exitCode: number | undefined, exitReason: TerminalExitReason): Promise<void> {
614 > // Release any cached terminal links and cancel in-flight link providers for this terminal extHostTerminalService.ts ×31
615 > this._terminalLinkCache.delete(id);
616 > const cancellationSource = this._terminalLinkCancellationSource.get(id);
617 > if (cancellationSource) {
618 > this._terminalLinkCancellationSource.delete(id);
619 > cancellationSource.dispose(true);
620 > }
621 >
622 > const index = this._getTerminalObjectIndexById(this._terminals, id);
623 > if (index !== null) {
624 > const terminal = this._terminals.splice(index, 1)[0];
625 > terminal.setExitStatus(exitCode, exitReason);
626 > this._onDidCloseTerminal.fire(terminal.value);
627 > }
628 > }
630 > public $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void {
631 > if (extHostTerminalId) { extHostTerminalService.ts ×31
632 // Resolve with the renderer generated id
633 const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId);
634 if (index !== null) {
635 // The terminal has already been created (via createTerminal*), only fire the event
636 this._terminals[index]._id = id;
637 this._onDidOpenTerminal.fire(this.terminals[index]);
638 this._terminals[index].isOpen = true;
639 return;
640 }
641 }
643 > const creationOptions: vscode.TerminalOptions = {
644 > name: shellLaunchConfigDto.name,
645 > shellPath: shellLaunchConfigDto.executable,
646 > shellArgs: shellLaunchConfigDto.args,
647 > cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd),
648 > env: shellLaunchConfigDto.env,
649 > hideFromUser: shellLaunchConfigDto.hideFromUser,
650 > titleTemplate: shellLaunchConfigDto.titleTemplate
651 > };
652 > const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name);
653 > this._terminals.push(terminal);
654 > this._onDidOpenTerminal.fire(terminal.value);
655 > terminal.isOpen = true;
656 > }
658 > public async $acceptTerminalProcessId(id: number, processId: number): Promise<void> {
659 const terminal = this.getTerminalById(id);
660 terminal?._setProcessId(processId);
661 }
663 > public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined> {
664 // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call
665 // Pseudoterminal.start
666 const terminal = this.getTerminalById(id);
667 if (!terminal) {
668 return { message: localize('launchFail.idMissingOnExtHost', "Could not find the terminal with id {0} on the extension host", id) };
669 }
670
671 // Wait for onDidOpenTerminal to fire
672 if (!terminal.isOpen) {
673 await new Promise<void>(r => {
674 // Ensure open is called after onDidOpenTerminal
675 const listener = this.onDidOpenTerminal(async e => {
676 if (e === terminal.value) {
677 listener.dispose();
678 r();
679 }
680 });
681 });
682 }
683
684 const terminalProcess = this._terminalProcesses.get(id);
685 if (terminalProcess) {
686 (terminalProcess as ExtHostPseudoterminal).startSendingEvents(initialDimensions);
687 } else {
688 // Defer startSendingEvents call to when _setupExtHostProcessListeners is called
689 this._extensionTerminalAwaitingStart[id] = { initialDimensions };
690 }
691
692 return undefined;
693 }
695 > protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): IDisposable {
696 const disposables = new DisposableStore();
697 disposables.add(p.onProcessReady(e => this._proxy.$sendProcessReady(id, e.pid, e.cwd, e.windowsPty)));
698 disposables.add(p.onDidChangeProperty(property => this._proxy.$sendProcessProperty(id, property)));
699
700 // Buffer data events to reduce the amount of messages going to the renderer
701 this._bufferer.startBuffering(id, p.onProcessData);
702 disposables.add(p.onProcessExit(exitCode => this._onProcessExit(id, exitCode)));
703 this._terminalProcesses.set(id, p);
704
705 const awaitingStart = this._extensionTerminalAwaitingStart[id];
706 if (awaitingStart && p instanceof ExtHostPseudoterminal) {
707 p.startSendingEvents(awaitingStart.initialDimensions);
708 delete this._extensionTerminalAwaitingStart[id];
709 }
710
711 return disposables;
712 }
714 > public $acceptProcessAckDataEvent(id: number, charCount: number): void {
715 this._terminalProcesses.get(id)?.acknowledgeDataEvent(charCount);
716 }
718 > public $acceptProcessInput(id: number, data: string): void {
719 this._terminalProcesses.get(id)?.input(data);
720 }
722 > public $acceptTerminalInteraction(id: number): void {
723 const terminal = this.getTerminalById(id);
724 if (terminal?.setInteractedWith()) {
725 this._onDidChangeTerminalState.fire(terminal.value);
726 }
727 }
729 > public $acceptTerminalSelection(id: number, selection: string | undefined): void {
730 this.getTerminalById(id)?.setSelection(selection);
731 }
733 > public $acceptProcessResize(id: number, cols: number, rows: number): void {
734 try {
735 this._terminalProcesses.get(id)?.resize(cols, rows);
736 } catch (error) {
737 // We tried to write to a closed pipe / channel.
738 if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
739 throw (error);
740 }
741 }
742 }
744 > public $acceptProcessShutdown(id: number, immediate: boolean): void {
745 this._terminalProcesses.get(id)?.shutdown(immediate);
746 }
748 > public $acceptProcessRequestInitialCwd(id: number): void {
749 this._terminalProcesses.get(id)?.getInitialCwd().then(initialCwd => this._proxy.$sendProcessProperty(id, { type: ProcessPropertyType.InitialCwd, value: initialCwd }));
750 }
752 > public $acceptProcessRequestCwd(id: number): void {
753 this._terminalProcesses.get(id)?.getCwd().then(cwd => this._proxy.$sendProcessProperty(id, { type: ProcessPropertyType.Cwd, value: cwd }));
754 }
756 > public $acceptProcessRequestLatency(id: number): Promise<number> {
757 return Promise.resolve(id);
758 }
760 >
761 > public registerProfileProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable {
762 if (this._profileProviders.has(id)) {
763 throw new Error(`Terminal profile provider "${id}" already registered`);
764 }
765 this._profileProviders.set(id, { provider, extension });
766 this._proxy.$registerProfileProvider(id, extension.identifier.value);
767 return new VSCodeDisposable(() => {
768 this._profileProviders.delete(id);
769 this._proxy.$unregisterProfileProvider(id);
770 });
771 }
773 > public registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider<TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable {
774 if (this._completionProviders.has(extension.identifier.value)) {
775 throw new Error(`Terminal completion provider "${extension.identifier.value}" already registered`);
776 }
777 this._completionProviders.set(extension.identifier.value, provider);
778 this._proxy.$registerCompletionProvider(extension.identifier.value, extension.identifier.value, ...triggerCharacters);
779 return new VSCodeDisposable(() => {
780 this._completionProviders.delete(extension.identifier.value);
781 this._proxy.$unregisterCompletionProvider(extension.identifier.value);
782 });
783 }
785 > public async $provideTerminalCompletions(id: string, options: ITerminalCompletionContextDto): Promise<TerminalCompletionListDto | undefined> {
786 const token = new CancellationTokenSource().token;
787 if (token.isCancellationRequested || !this.activeTerminal) {
788 return undefined;
789 }
790
791 const provider = this._completionProviders.get(id);
792 if (!provider) {
793 return;
794 }
795
796 const completions = await provider.provideTerminalCompletions(this.activeTerminal, options, token);
797 if (completions === null || completions === undefined) {
798 return undefined;
799 }
800 const pathSeparator = !isWindows || this.activeTerminal.state?.shell === WindowsShellType.GitBash ? '/' : '\\';
801 return TerminalCompletionList.from(completions, pathSeparator);
802 }
804 > public $acceptTerminalShellType(id: number, shellType: TerminalShellType | undefined): void {
805 const terminal = this.getTerminalById(id);
806 if (terminal?.setShellType(shellType)) {
807 this._onDidChangeTerminalState.fire(terminal.value);
808 }
809 }
811 > public registerTerminalQuickFixProvider(id: string, extensionId: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable {
812 if (this._quickFixProviders.has(id)) {
813 throw new Error(`Terminal quick fix provider "${id}" is already registered`);
814 }
815 this._quickFixProviders.set(id, provider);
816 this._proxy.$registerQuickFixProvider(id, extensionId);
817 return new VSCodeDisposable(() => {
818 this._quickFixProviders.delete(id);
819 this._proxy.$unregisterQuickFixProvider(id);
820 });
821 }
823 > public async $provideTerminalQuickFixes(id: string, matchResult: TerminalCommandMatchResultDto): Promise<(ITerminalQuickFixTerminalCommandDto | ITerminalQuickFixOpenerDto | ICommandDto)[] | ITerminalQuickFixTerminalCommandDto | ITerminalQuickFixOpenerDto | ICommandDto | undefined> {
824 const token = new CancellationTokenSource().token;
825 if (token.isCancellationRequested) {
826 return;
827 }
828 const provider = this._quickFixProviders.get(id);
829 if (!provider) {
830 return;
831 }
832 const quickFixes = await provider.provideTerminalQuickFixes(matchResult, token);
833 if (quickFixes === null || (Array.isArray(quickFixes) && quickFixes.length === 0)) {
834 return undefined;
835 }
836
837 const store = new DisposableStore();
838 this._lastQuickFixCommands.value = store;
839
840 // Single
841 if (!Array.isArray(quickFixes)) {
842 return quickFixes ? TerminalQuickFix.from(quickFixes, this._extHostCommands.converter, store) : undefined;
843 }
844
845 // Many
846 const result = [];
847 for (const fix of quickFixes) {
848 const converted = TerminalQuickFix.from(fix, this._extHostCommands.converter, store);
849 if (converted) {
850 result.push(converted);
851 }
852 }
853 return result;
854 }
856 > public async $createContributedProfileTerminal(id: string, options: ICreateContributedTerminalProfileOptions): Promise<void> {
857 const token = new CancellationTokenSource().token;
858 const profileProviderData = this._profileProviders.get(id);
859 if (!profileProviderData) {
860 throw new Error(`No terminal profile provider registered for id "${id}"`);
861 }
862 let profile = await profileProviderData.provider.provideTerminalProfile(token);
863 if (token.isCancellationRequested) {
864 return;
865 }
866 if (profile && !hasKey(profile, { options: true })) {
867 profile = { options: profile };
868 }
869
870 if (!profile || !hasKey(profile, { options: true })) {
871 throw new Error(`No terminal profile options provided for id "${id}"`);
872 }
873
874 const hasTerminalTitleProposal = isProposedApiEnabled(profileProviderData.extension, 'terminalTitle');
875 if (!hasTerminalTitleProposal && profile.options.titleTemplate !== undefined) {
876 console.error(`[${profileProviderData.extension.identifier.value}] \`titleTemplate\` returned from TerminalProfileProvider is ignored because the \`terminalTitle\` proposed API is not enabled.`);
877 profile = { options: { ...profile.options, titleTemplate: undefined } };
878 }
879 // options.titleTemplate is not explicitly stripped here because the profileOptions
880 // assignment below only applies it when hasTerminalTitleProposal is true.
881 if (!hasTerminalTitleProposal && options.titleTemplate !== undefined) {
882 console.error(`[${profileProviderData.extension.identifier.value}] \`titleTemplate\` passed to createContributedTerminalProfile is ignored because the \`terminalTitle\` proposed API is not enabled.`);
883 }
884
885 const profileOptions = hasTerminalTitleProposal && options.titleTemplate && !profile.options.titleTemplate
886 ? { ...profile.options, titleTemplate: options.titleTemplate }
887 : profile.options;
888
889 if (hasKey(profileOptions, { pty: true })) {
890 this.createExtensionTerminal(profileOptions, options);
891 return;
892 }
893 this.createTerminalFromOptions(profileOptions, options);
894 }
896 > public registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable {
897 > this._linkProviders.add(provider); extHostTerminalService.ts ×31
898 > if (this._linkProviders.size === 1) {
899 > this._proxy.$startLinkProvider();
900 > }
901 > return new VSCodeDisposable(() => {
902 > this._linkProviders.delete(provider);
903 > if (this._linkProviders.size === 0) {
904 > this._proxy.$stopLinkProvider();
905 > }
906 > });
907 > }
909 > public async $provideLinks(terminalId: number, line: string): Promise<ITerminalLinkDto[]> {
910 > const terminal = this.getTerminalById(terminalId); extHostTerminalService.ts ×31
911 > if (!terminal) {
912 > return [];
913 > }
914 >
915 > // Discard any cached links the terminal has been holding, currently all links are released
916 > // when new links are provided.
917 > this._terminalLinkCache.delete(terminalId);
918 >
919 > const oldToken = this._terminalLinkCancellationSource.get(terminalId);
920 > oldToken?.dispose(true);
921 > const cancellationSource = new CancellationTokenSource();
922 > this._terminalLinkCancellationSource.set(terminalId, cancellationSource);
923 >
924 > const result: ITerminalLinkDto[] = [];
925 > const context: vscode.TerminalLinkContext = { terminal: terminal.value, line };
926 > const promises: vscode.ProviderResult<{ provider: vscode.TerminalLinkProvider; links: vscode.TerminalLink[] }>[] = [];
927 >
928 > for (const provider of this._linkProviders) {
929 > promises.push(Promises.withAsyncBody(async r => {
930 > const cancelSubscription = cancellationSource.token.onCancellationRequested(() => r({ provider, links: [] }));
931 > try {
932 > const links = (await provider.provideTerminalLinks(context, cancellationSource.token)) || [];
933 > if (!cancellationSource.token.isCancellationRequested) {
934 r({ provider, links });
935 }
936 > } finally { extHostTerminalService.ts ×31
937 > cancelSubscription.dispose();
938 > }
939 > }));
940 > }
941 >
942 > const provideResults = await Promise.all(promises);
943 >
944 > if (cancellationSource.token.isCancellationRequested) {
945 > return [];
946 > }
947
948 const cacheLinkMap = new Map<number, ICachedLinkEntry>();
949 for (const provideResult of provideResults) {
950 if (provideResult && provideResult.links.length > 0) {
951 result.push(...provideResult.links.map(providerLink => {
952 const link = {
953 id: nextLinkId++,
954 startIndex: providerLink.startIndex,
955 length: providerLink.length,
956 label: providerLink.tooltip
957 };
958 cacheLinkMap.set(link.id, {
959 provider: provideResult.provider,
960 link: providerLink
961 });
962 return link;
963 }));
964 }
965 }
966
967 this._terminalLinkCache.set(terminalId, cacheLinkMap);
968
969 return result;
972 > $activateLink(terminalId: number, linkId: number): void {
973 > const cachedLink = this._terminalLinkCache.get(terminalId)?.get(linkId); extHostTerminalService.ts ×31
974 > if (!cachedLink) {
975 > return;
976 > }
977 cachedLink.provider.handleTerminalLink(cachedLink.link);
980 > private _onProcessExit(id: number, exitCode: number | undefined): void {
981 this._bufferer.stopBuffering(id);
982
983 // Remove process reference
984 this._terminalProcesses.delete(id);
985 delete this._extensionTerminalAwaitingStart[id];
986
987 // Clean up process disposables
988 const processDiposable = this._terminalProcessDisposables[id];
989 if (processDiposable) {
990 processDiposable.dispose();
991 delete this._terminalProcessDisposables[id];
992 }
993 // Send exit event to main side
994 this._proxy.$sendProcessExit(id, exitCode);
995 }
997 > public getTerminalById(id: number): ExtHostTerminal | null {
998 > return this._getTerminalObjectById(this._terminals, id); extHostTerminalService.ts ×31
999 > }
1001 > public getTerminalIdByApiObject(terminal: vscode.Terminal): number | null {
1002 const index = this._terminals.findIndex(item => {
1003 return item.value === terminal;
1004 });
1005 return index >= 0 ? index : null;
1006 }
1008 > private _getTerminalObjectById<T extends ExtHostTerminal>(array: T[], id: number): T | null {
1009 > const index = this._getTerminalObjectIndexById(array, id); extHostTerminalService.ts ×31
1010 > return index !== null ? array[index] : null;
1011 > }
1013 > private _getTerminalObjectIndexById<T extends ExtHostTerminal>(array: T[], id: ExtHostTerminalIdentifier): number | null {
1014 > const index = array.findIndex(item => { extHostTerminalService.ts ×31
1015 > return item._id === id;
1016 > });
1017 > return index >= 0 ? index : null;
1018 > }
1020 > public getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection {
1021 let collection = this._environmentVariableCollections.get(extension.identifier.value);
1022 if (!collection) {
1023 collection = this._register(new UnifiedEnvironmentVariableCollection());
1024 this._setEnvironmentVariableCollection(extension.identifier.value, collection);
1025 }
1026 return collection.getScopedEnvironmentVariableCollection(undefined);
1027 }
1029 > private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: UnifiedEnvironmentVariableCollection): void {
1030 const serialized = serializeEnvironmentVariableCollection(collection.map);
1031 const serializedDescription = serializeEnvironmentDescriptionMap(collection.descriptionMap);
1032 this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized, serializedDescription);
1033 }
1035 > public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void {
1036 collections.forEach(entry => {
1037 const extensionIdentifier = entry[0];
1038 const collection = this._register(new UnifiedEnvironmentVariableCollection(entry[1]));
1039 this._setEnvironmentVariableCollection(extensionIdentifier, collection);
1040 });
1041 }
1043 > public $acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void {
1044 const oldProfile = this._defaultProfile;
1045 this._defaultProfile = profile;
1046 this._defaultAutomationProfile = automationProfile;
1047 if (oldProfile?.path !== profile.path) {
1048 this._onDidChangeShell.fire(profile.path);
1049 }
1050 }
1052 > private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: UnifiedEnvironmentVariableCollection): void {
1053 this._environmentVariableCollections.set(extensionIdentifier, collection);
1054 this._register(collection.onDidChangeCollection(() => {
1055 // When any collection value changes send this immediately, this is done to ensure
1056 // following calls to createTerminal will be created with the new environment. It will
1057 // result in more noise by sending multiple updates when called but collections are
1058 // expected to be small.
1059 this._syncEnvironmentVariableCollection(extensionIdentifier, collection);
1060 }));
1061 }
1063 >
1064 > /**
1065 > * Unified environment variable collection carrying information for all scopes, for a specific extension.
1066 > */
1067 > class UnifiedEnvironmentVariableCollection extends Disposable {
1068 > readonly map: Map<string, IEnvironmentVariableMutator> = new Map();
1069 > private readonly scopedCollections: Map<string, ScopedEnvironmentVariableCollection> = new Map();
1070 > readonly descriptionMap: Map<string, IEnvironmentVariableCollectionDescription> = new Map();
1071 > private _persistent: boolean = true;
1072 >
1073 > public get persistent(): boolean { return this._persistent; }
1074 > public set persistent(value: boolean) {
1075 this._persistent = value;
1076 this._onDidChangeCollection.fire();
1077 }
1079 > protected readonly _onDidChangeCollection: Emitter<void> = this._register(new Emitter<void>());
1080 > get onDidChangeCollection(): Event<void> { return this._onDidChangeCollection && this._onDidChangeCollection.event; }
1081 >
1082 > constructor(
1083 serialized?: ISerializableEnvironmentVariableCollection
1084 ) {
1085 super();
1086 this.map = new Map(serialized);
1087 }
1089 > getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): IEnvironmentVariableCollection {
1090 const scopedCollectionKey = this.getScopeKey(scope);
1091 let scopedCollection = this.scopedCollections.get(scopedCollectionKey);
1092 if (!scopedCollection) {
1093 scopedCollection = new ScopedEnvironmentVariableCollection(this, scope);
1094 this.scopedCollections.set(scopedCollectionKey, scopedCollection);
1095 this._register(scopedCollection.onDidChangeCollection(() => this._onDidChangeCollection.fire()));
1096 }
1097 return scopedCollection;
1098 }
1100 > replace(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
1101 this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace, options: options ?? { applyAtProcessCreation: true }, scope });
1102 }
1104 > append(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
1105 this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append, options: options ?? { applyAtProcessCreation: true }, scope });
1106 }
1108 > prepend(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
1109 this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend, options: options ?? { applyAtProcessCreation: true }, scope });
1110 }
1112 > private _setIfDiffers(variable: string, mutator: vscode.EnvironmentVariableMutator & { scope: vscode.EnvironmentVariableScope | undefined }): void {
1113 if (mutator.options && mutator.options.applyAtProcessCreation === false && !mutator.options.applyAtShellIntegration) {
1114 throw new Error('EnvironmentVariableMutatorOptions must apply at either process creation or shell integration');
1115 }
1116 const key = this.getKey(variable, mutator.scope);
1117 const current = this.map.get(key);
1118 const newOptions = mutator.options ? {
1119 applyAtProcessCreation: mutator.options.applyAtProcessCreation ?? false,
1120 applyAtShellIntegration: mutator.options.applyAtShellIntegration ?? false,
1121 } : {
1122 applyAtProcessCreation: true
1123 };
1124 if (
1125 !current ||
1126 current.value !== mutator.value ||
1127 current.type !== mutator.type ||
1128 current.options?.applyAtProcessCreation !== newOptions.applyAtProcessCreation ||
1129 current.options?.applyAtShellIntegration !== newOptions.applyAtShellIntegration ||
1130 current.scope?.workspaceFolder?.index !== mutator.scope?.workspaceFolder?.index
1131 ) {
1132 const key = this.getKey(variable, mutator.scope);
1133 const value: IEnvironmentVariableMutator = {
1134 variable,
1135 ...mutator,
1136 options: newOptions
1137 };
1138 this.map.set(key, value);
1139 this._onDidChangeCollection.fire();
1140 }
1141 }
1143 > get(variable: string, scope: vscode.EnvironmentVariableScope | undefined): vscode.EnvironmentVariableMutator | undefined {
1144 const key = this.getKey(variable, scope);
1145 const value = this.map.get(key);
1146 // TODO: Set options to defaults if needed
1147 return value ? convertMutator(value) : undefined;
1148 }
1150 > private getKey(variable: string, scope: vscode.EnvironmentVariableScope | undefined) {
1151 const scopeKey = this.getScopeKey(scope);
1152 return scopeKey.length ? `${variable}:::${scopeKey}` : variable;
1153 }
1155 > private getScopeKey(scope: vscode.EnvironmentVariableScope | undefined): string {
1156 return this.getWorkspaceKey(scope?.workspaceFolder) ?? '';
1157 }
1159 > private getWorkspaceKey(workspaceFolder: vscode.WorkspaceFolder | undefined): string | undefined {
1160 return workspaceFolder ? workspaceFolder.uri.toString() : undefined;
1161 }
1163 > public getVariableMap(scope: vscode.EnvironmentVariableScope | undefined): Map<string, vscode.EnvironmentVariableMutator> {
1164 const map = new Map<string, vscode.EnvironmentVariableMutator>();
1165 for (const [_, value] of this.map) {
1166 if (this.getScopeKey(value.scope) === this.getScopeKey(scope)) {
1167 map.set(value.variable, convertMutator(value));
1168 }
1169 }
1170 return map;
1171 }
1173 > delete(variable: string, scope: vscode.EnvironmentVariableScope | undefined): void {
1174 const key = this.getKey(variable, scope);
1175 this.map.delete(key);
1176 this._onDidChangeCollection.fire();
1177 }
1179 > clear(scope: vscode.EnvironmentVariableScope | undefined): void {
1180 if (scope?.workspaceFolder) {
1181 for (const [key, mutator] of this.map) {
1182 if (mutator.scope?.workspaceFolder?.index === scope.workspaceFolder.index) {
1183 this.map.delete(key);
1184 }
1185 }
1186 this.clearDescription(scope);
1187 } else {
1188 this.map.clear();
1189 this.descriptionMap.clear();
1190 }
1191 this._onDidChangeCollection.fire();
1192 }
1194 > setDescription(description: string | vscode.MarkdownString | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
1195 const key = this.getScopeKey(scope);
1196 const current = this.descriptionMap.get(key);
1197 if (!current || current.description !== description) {
1198 let descriptionStr: string | undefined;
1199 if (typeof description === 'string') {
1200 descriptionStr = description;
1201 } else {
1202 // Only take the description before the first `\n\n`, so that the description doesn't mess up the UI
1203 descriptionStr = description?.value.split('\n\n')[0];
1204 }
1205 const value: IEnvironmentVariableCollectionDescription = { description: descriptionStr, scope };
1206 this.descriptionMap.set(key, value);
1207 this._onDidChangeCollection.fire();
1208 }
1209 }
1211 > public getDescription(scope: vscode.EnvironmentVariableScope | undefined): string | vscode.MarkdownString | undefined {
1212 const key = this.getScopeKey(scope);
1213 return this.descriptionMap.get(key)?.description;
1214 }
1216 > private clearDescription(scope: vscode.EnvironmentVariableScope | undefined): void {
1217 const key = this.getScopeKey(scope);
1218 this.descriptionMap.delete(key);
1219 }
1221 >
1222 > class ScopedEnvironmentVariableCollection implements IEnvironmentVariableCollection {
1223 > public get persistent(): boolean { return this.collection.persistent; }
1224 > public set persistent(value: boolean) {
1225 this.collection.persistent = value;
1226 }
1228 > protected readonly _onDidChangeCollection = new Emitter<void>();
1229 > get onDidChangeCollection(): Event<void> { return this._onDidChangeCollection && this._onDidChangeCollection.event; }
1230 >
1231 > constructor(
1232 private readonly collection: UnifiedEnvironmentVariableCollection,
1233 private readonly scope: vscode.EnvironmentVariableScope | undefined
1234 ) {
1235 }
1237 > getScoped(scope: vscode.EnvironmentVariableScope | undefined) {
1238 return this.collection.getScopedEnvironmentVariableCollection(scope);
1239 }
1241 > replace(variable: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions | undefined): void {
1242 this.collection.replace(variable, value, options, this.scope);
1243 }
1245 > append(variable: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions | undefined): void {
1246 this.collection.append(variable, value, options, this.scope);
1247 }
1249 > prepend(variable: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions | undefined): void {
1250 this.collection.prepend(variable, value, options, this.scope);
1251 }
1253 > get(variable: string): vscode.EnvironmentVariableMutator | undefined {
1254 return this.collection.get(variable, this.scope);
1255 }
1257 > forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => unknown, thisArg?: unknown): void {
1258 this.collection.getVariableMap(this.scope).forEach((value, variable) => callback.call(thisArg, variable, value, this), this.scope);
1259 }
1261 > [Symbol.iterator](): IterableIterator<[variable: string, mutator: vscode.EnvironmentVariableMutator]> {
1262 return this.collection.getVariableMap(this.scope).entries();
1263 }
1265 > delete(variable: string): void {
1266 this.collection.delete(variable, this.scope);
1267 this._onDidChangeCollection.fire(undefined);
1268 }
1270 > clear(): void {
1271 this.collection.clear(this.scope);
1272 }
1274 > set description(description: string | vscode.MarkdownString | undefined) {
1275 this.collection.setDescription(description, this.scope);
1276 }
1278 > get description(): string | vscode.MarkdownString | undefined {
1279 return this.collection.getDescription(this.scope);
1280 }
1282 >
1283 > export class WorkerExtHostTerminalService extends BaseExtHostTerminalService {
1284 >
1285 > private readonly _hasRemoteAuthority: boolean;
1286 >
1287 > constructor(
1288 > @IExtHostCommands extHostCommands: IExtHostCommands, extHostTerminalService.ts ×31
1289 > @IExtHostRpcService extHostRpc: IExtHostRpcService,
1290 > @IExtHostInitDataService initData: IExtHostInitDataService
1291 > ) {
1292 > super(false, extHostCommands, extHostRpc);
1293 > this._hasRemoteAuthority = !!initData.remote.authority;
1294 > }
1296 > public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
1297 if (!this._hasRemoteAuthority) {
1298 throw new NotSupportedError();
1299 }
1300 return this.createTerminalFromOptions({ name, shellPath, shellArgs });
1301 }
1303 > public createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal {
1304 if (!this._hasRemoteAuthority) {
1305 throw new NotSupportedError();
1306 }
1307 const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
1308 this._terminals.push(terminal);
1309 terminal.create(options, this._serializeParentTerminal(options, internalOptions));
1310 return terminal.value;
1311 }
1313 >
1314 function asTerminalIcon(iconPath?: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon): TerminalIcon | undefined {
1315 if (!iconPath || typeof iconPath === 'string') {
1316 return undefined;
1317 }
1318
1319 if (!hasKey(iconPath, { id: true })) {
1320 return iconPath;
1321 }
1322
1323 return {
1324 id: iconPath.id,
1325 color: iconPath.color as ThemeColor
1326 };
1327 }
1329 function asTerminalColor(color?: vscode.ThemeColor): ThemeColor | undefined {
1330 return ThemeColor.isThemeColor(color) ? color as ThemeColor : undefined;
1331 }
1333 function convertMutator(mutator: IEnvironmentVariableMutator): vscode.EnvironmentVariableMutator {
1334 const newMutator = { ...mutator };
1335 delete newMutator.scope;
1336 newMutator.options = newMutator.options ?? undefined;
1337 return newMutator as vscode.EnvironmentVariableMutator;
1338 }