mirror of
https://github.com/Microsoft/vscode
synced 2024-09-18 01:58:27 +00:00
Merge pull request #123729 from microsoft/tyriar/getprofilesexthost
Move profile resolving to pty service
This commit is contained in:
commit
c185bff400
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.57.0",
|
||||
"distro": "32e7f991e227138d9f08ed5bf19f34e6a5192dfd",
|
||||
"distro": "d4088d7fa3ad4446065d4cec45d90d02d04fe745",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
|
|
@ -272,7 +272,7 @@ class SharedProcessMain extends Disposable {
|
|||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
|
||||
// Terminal
|
||||
services.set(ILocalPtyService, this._register(new PtyHostService(logService, telemetryService)));
|
||||
services.set(ILocalPtyService, this._register(new PtyHostService(configurationService, logService, telemetryService)));
|
||||
|
||||
return new InstantiationService(services);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,78 @@ import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
|
|||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
|
||||
|
||||
export const enum TerminalSettingId {
|
||||
ShellLinux = 'terminal.integrated.shell.linux',
|
||||
ShellMacOs = 'terminal.integrated.shell.osx',
|
||||
ShellWindows = 'terminal.integrated.shell.windows',
|
||||
SendKeybindingsToShell = 'terminal.integrated.sendKeybindingsToShell',
|
||||
AutomationShellLinux = 'terminal.integrated.automationShell.linux',
|
||||
AutomationShellMacOs = 'terminal.integrated.automationShell.osx',
|
||||
AutomationShellWindows = 'terminal.integrated.automationShell.windows',
|
||||
ShellArgsLinux = 'terminal.integrated.shellArgs.linux',
|
||||
ShellArgsMacOs = 'terminal.integrated.shellArgs.osx',
|
||||
ShellArgsWindows = 'terminal.integrated.shellArgs.windows',
|
||||
ProfilesWindows = 'terminal.integrated.profiles.windows',
|
||||
ProfilesMacOs = 'terminal.integrated.profiles.osx',
|
||||
ProfilesLinux = 'terminal.integrated.profiles.linux',
|
||||
DefaultProfileLinux = 'terminal.integrated.defaultProfile.linux',
|
||||
DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx',
|
||||
DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows',
|
||||
UseWslProfiles = 'terminal.integrated.useWslProfiles',
|
||||
TabsEnabled = 'terminal.integrated.tabs.enabled',
|
||||
TabsHideCondition = 'terminal.integrated.tabs.hideCondition',
|
||||
TabsShowActiveTerminal = 'terminal.integrated.tabs.showActiveTerminal',
|
||||
TabsLocation = 'terminal.integrated.tabs.location',
|
||||
TabsFocusMode = 'terminal.integrated.tabs.focusMode',
|
||||
MacOptionIsMeta = 'terminal.integrated.macOptionIsMeta',
|
||||
MacOptionClickForcesSelection = 'terminal.integrated.macOptionClickForcesSelection',
|
||||
AltClickMovesCursor = 'terminal.integrated.altClickMovesCursor',
|
||||
CopyOnSelection = 'terminal.integrated.copyOnSelection',
|
||||
DrawBoldTextInBrightColors = 'terminal.integrated.drawBoldTextInBrightColors',
|
||||
FontFamily = 'terminal.integrated.fontFamily',
|
||||
FontSize = 'terminal.integrated.fontSize',
|
||||
LetterSpacing = 'terminal.integrated.letterSpacing',
|
||||
LineHeight = 'terminal.integrated.lineHeight',
|
||||
MinimumContrastRatio = 'terminal.integrated.minimumContrastRatio',
|
||||
FastScrollSensitivity = 'terminal.integrated.fastScrollSensitivity',
|
||||
MouseWheelScrollSensitivity = 'terminal.integrated.mouseWheelScrollSensitivity',
|
||||
BellDuration = 'terminal.integrated.bellDuration',
|
||||
FontWeight = 'terminal.integrated.fontWeight',
|
||||
FontWeightBold = 'terminal.integrated.fontWeightBold',
|
||||
CursorBlinking = 'terminal.integrated.cursorBlinking',
|
||||
CursorStyle = 'terminal.integrated.cursorStyle',
|
||||
CursorWidth = 'terminal.integrated.cursorWidth',
|
||||
Scrollback = 'terminal.integrated.scrollback',
|
||||
DetectLocale = 'terminal.integrated.detectLocale',
|
||||
GpuAcceleration = 'terminal.integrated.gpuAcceleration',
|
||||
RightClickBehavior = 'terminal.integrated.rightClickBehavior',
|
||||
Cwd = 'terminal.integrated.cwd',
|
||||
ConfirmOnExit = 'terminal.integrated.confirmOnExit',
|
||||
EnableBell = 'terminal.integrated.enableBell',
|
||||
CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell',
|
||||
AllowChords = 'terminal.integrated.allowChords',
|
||||
AllowMnemonics = 'terminal.integrated.allowMnemonics',
|
||||
EnvMacOs = 'terminal.integrated.env.osx',
|
||||
EnvLinux = 'terminal.integrated.env.linux',
|
||||
EnvWindows = 'terminal.integrated.env.windows',
|
||||
EnvironmentChangesIndicator = 'terminal.integrated.environmentChangesIndicator',
|
||||
EnvironmentChangesRelaunch = 'terminal.integrated.environmentChangesRelaunch',
|
||||
ShowExitAlert = 'terminal.integrated.showExitAlert',
|
||||
SplitCwd = 'terminal.integrated.splitCwd',
|
||||
WindowsEnableConpty = 'terminal.integrated.windowsEnableConpty',
|
||||
WordSeparators = 'terminal.integrated.wordSeparators',
|
||||
ExperimentalUseTitleEvent = 'terminal.integrated.experimentalUseTitleEvent',
|
||||
EnableFileLinks = 'terminal.integrated.enableFileLinks',
|
||||
UnicodeVersion = 'terminal.integrated.unicodeVersion',
|
||||
ExperimentalLinkProvider = 'terminal.integrated.experimentalLinkProvider',
|
||||
LocalEchoLatencyThreshold = 'terminal.integrated.localEchoLatencyThreshold',
|
||||
LocalEchoExcludePrograms = 'terminal.integrated.localEchoExcludePrograms',
|
||||
LocalEchoStyle = 'terminal.integrated.localEchoStyle',
|
||||
EnablePersistentSessions = 'terminal.integrated.enablePersistentSessions',
|
||||
AllowWorkspaceConfiguration = 'terminal.integrated.allowWorkspaceConfiguration',
|
||||
InheritEnv = 'terminal.integrated.inheritEnv'
|
||||
}
|
||||
|
||||
export enum WindowsShellType {
|
||||
CommandPrompt = 'cmd',
|
||||
PowerShell = 'pwsh',
|
||||
|
@ -96,6 +168,7 @@ export interface IOffProcessTerminalService {
|
|||
attachToProcess(id: number): Promise<ITerminalChildProcess | undefined>;
|
||||
listProcesses(): Promise<IProcessDetails[]>;
|
||||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
|
||||
getProfiles(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
|
||||
getWslPath(original: string): Promise<string>;
|
||||
getEnvironment(): Promise<IProcessEnvironment>;
|
||||
getShellEnvironment(): Promise<IProcessEnvironment | undefined>;
|
||||
|
@ -119,6 +192,8 @@ export interface IPtyService {
|
|||
readonly onPtyHostStart?: Event<void>;
|
||||
readonly onPtyHostUnresponsive?: Event<void>;
|
||||
readonly onPtyHostResponsive?: Event<void>;
|
||||
readonly onPtyHostRequestResolveVariables?: Event<IRequestResolveVariablesEvent>;
|
||||
|
||||
readonly onProcessData: Event<{ id: number, event: IProcessDataEvent | string }>;
|
||||
readonly onProcessExit: Event<{ id: number, event: number | undefined }>;
|
||||
readonly onProcessReady: Event<{ id: number, event: { pid: number, cwd: string } }>;
|
||||
|
@ -131,6 +206,7 @@ export interface IPtyService {
|
|||
|
||||
restartPtyHost?(): Promise<void>;
|
||||
shutdownAll?(): Promise<void>;
|
||||
acceptPtyHostResolvedVariables?(id: number, resolved: string[]): Promise<void>;
|
||||
|
||||
createProcess(
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
|
@ -163,9 +239,10 @@ export interface IPtyService {
|
|||
processBinary(id: number, data: string): Promise<void>;
|
||||
/** Confirm the process is _not_ an orphan. */
|
||||
orphanQuestionReply(id: number): Promise<void>;
|
||||
updateTitle(id: number, title: string): Promise<void>
|
||||
updateIcon(id: number, icon: string, color?: string): Promise<void>
|
||||
updateTitle(id: number, title: string): Promise<void>;
|
||||
updateIcon(id: number, icon: string, color?: string): Promise<void>;
|
||||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
|
||||
getProfiles?(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
|
||||
getEnvironment(): Promise<IProcessEnvironment>;
|
||||
getWslPath(original: string): Promise<string>;
|
||||
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void>;
|
||||
|
@ -173,6 +250,11 @@ export interface IPtyService {
|
|||
reduceConnectionGraceTime(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IRequestResolveVariablesEvent {
|
||||
id: number;
|
||||
originalText: string[];
|
||||
}
|
||||
|
||||
export enum HeartbeatConstants {
|
||||
/**
|
||||
* The duration between heartbeats
|
||||
|
@ -453,6 +535,17 @@ export interface ITerminalDimensions {
|
|||
rows: number;
|
||||
}
|
||||
|
||||
export interface ITerminalProfile {
|
||||
profileName: string;
|
||||
path: string;
|
||||
isDefault: boolean;
|
||||
isAutoDetected?: boolean;
|
||||
args?: string | string[] | undefined;
|
||||
env?: ITerminalEnvironment;
|
||||
overrideName?: boolean;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface ITerminalDimensionsOverride extends Readonly<ITerminalDimensions> {
|
||||
/**
|
||||
* indicate that xterm must receive these exact dimensions, even if they overflow the ui!
|
||||
|
@ -461,3 +554,26 @@ export interface ITerminalDimensionsOverride extends Readonly<ITerminalDimension
|
|||
}
|
||||
|
||||
export type SafeConfigProvider = <T>(key: string) => T | undefined;
|
||||
|
||||
export const enum ProfileSource {
|
||||
GitBash = 'Git Bash',
|
||||
Pwsh = 'PowerShell'
|
||||
}
|
||||
|
||||
export interface IBaseUnresolvedTerminalProfile {
|
||||
args?: string | string[] | undefined;
|
||||
isAutoDetected?: boolean;
|
||||
overrideName?: boolean;
|
||||
icon?: string;
|
||||
env?: ITerminalEnvironment;
|
||||
}
|
||||
|
||||
export interface ITerminalExecutable extends IBaseUnresolvedTerminalProfile {
|
||||
path: string | string[];
|
||||
}
|
||||
|
||||
export interface ITerminalProfileSource extends IBaseUnresolvedTerminalProfile {
|
||||
source: ProfileSource;
|
||||
}
|
||||
|
||||
export type ITerminalProfileObject = ITerminalExecutable | ITerminalProfileSource | null;
|
||||
|
|
326
src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
Normal file
326
src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
Normal file
|
@ -0,0 +1,326 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
const terminalProfileSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
required: ['path'],
|
||||
properties: {
|
||||
path: {
|
||||
description: localize('terminalProfile.path', 'A single path to a shell executable or an array of paths that will be used as fallbacks when one fails.'),
|
||||
type: ['string', 'array'],
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
args: {
|
||||
description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
overrideName: {
|
||||
description: localize('terminalProfile.overrideName', 'Controls whether or not the profile name overrides the auto detected one.'),
|
||||
type: 'boolean'
|
||||
},
|
||||
icon: {
|
||||
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
|
||||
type: 'string'
|
||||
},
|
||||
env: {
|
||||
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: ['string', 'null']
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const shellDeprecationMessageLinux = localize('terminal.integrated.shell.linux.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.linux#`', '`#terminal.integrated.defaultProfile.linux#`');
|
||||
const shellDeprecationMessageOsx = localize('terminal.integrated.shell.osx.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.osx#`', '`#terminal.integrated.defaultProfile.osx#`');
|
||||
const shellDeprecationMessageWindows = localize('terminal.integrated.shell.windows.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.windows#`', '`#terminal.integrated.defaultProfile.windows#`');
|
||||
|
||||
const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
id: 'terminal',
|
||||
order: 100,
|
||||
title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
[TerminalSettingId.AutomationShellLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.linux',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.AutomationShellMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.osx',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.AutomationShellWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.windows',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.ShellLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageLinux
|
||||
},
|
||||
[TerminalSettingId.ShellMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageOsx
|
||||
},
|
||||
[TerminalSettingId.ShellWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageWindows
|
||||
},
|
||||
[TerminalSettingId.ShellArgsLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
default: [],
|
||||
markdownDeprecationMessage: shellDeprecationMessageLinux
|
||||
},
|
||||
[TerminalSettingId.ShellArgsMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
// Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This
|
||||
// is the reason terminals on macOS typically run login shells by default which set up
|
||||
// the environment. See http://unix.stackexchange.com/a/119675/115410
|
||||
default: ['-l'],
|
||||
markdownDeprecationMessage: shellDeprecationMessageOsx
|
||||
},
|
||||
[TerminalSettingId.ShellArgsWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
'anyOf': [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows.string', "The command line arguments in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6) to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
|
||||
}
|
||||
],
|
||||
default: [],
|
||||
markdownDeprecationMessage: shellDeprecationMessageWindows
|
||||
},
|
||||
[TerminalSettingId.ProfilesWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profiles.windows',
|
||||
comment: ['{0}, {1}, and {2} are the `source`, `path` and optional `args` settings keys']
|
||||
},
|
||||
"The Windows profiles to present when creating a new terminal via the terminal dropdown. Set to null to exclude them, use the {0} property to use the default detected configuration. Or, set the {1} and optional {2}", '`source`', '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'PowerShell': {
|
||||
source: 'PowerShell',
|
||||
icon: 'terminal-powershell'
|
||||
},
|
||||
'Command Prompt': {
|
||||
path: [
|
||||
'${env:windir}\\Sysnative\\cmd.exe',
|
||||
'${env:windir}\\System32\\cmd.exe'
|
||||
],
|
||||
args: [],
|
||||
icon: 'terminal-cmd'
|
||||
},
|
||||
'Git Bash': {
|
||||
source: 'Git Bash'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['source'],
|
||||
properties: {
|
||||
source: {
|
||||
description: localize('terminalProfile.windowsSource', 'A profile source that will auto detect the paths to the shell.'),
|
||||
enum: ['PowerShell', 'Git Bash']
|
||||
},
|
||||
overrideName: {
|
||||
description: localize('terminalProfile.overrideName', 'Controls whether or not the profile name overrides the auto detected one.'),
|
||||
type: 'boolean'
|
||||
},
|
||||
icon: {
|
||||
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
|
||||
type: 'string'
|
||||
},
|
||||
env: {
|
||||
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: ['string', 'null']
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.ProfilesMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profile.osx',
|
||||
comment: ['{0} and {1} are the `path` and optional `args` settings keys']
|
||||
},
|
||||
"The macOS profiles to present when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0} and optional {1}", '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash',
|
||||
icon: 'terminal-bash'
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh'
|
||||
},
|
||||
'fish': {
|
||||
path: 'fish'
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.ProfilesLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profile.linux',
|
||||
comment: ['{0} and {1} are the `path` and optional `args` settings keys']
|
||||
},
|
||||
"The Linux profiles to present when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0} and optional {1}", '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash'
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh'
|
||||
},
|
||||
'fish': {
|
||||
path: 'fish'
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.defaultProfile.linux', "The default profile used on Linux. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.linux#`', '`#terminal.integrated.shellArgs.linux#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileMacOs]: {
|
||||
restricted: true,
|
||||
description: localize('terminal.integrated.defaultProfile.osx', "The default profile used on macOS. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.osx#`', '`#terminal.integrated.shellArgs.osx#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileWindows]: {
|
||||
restricted: true,
|
||||
description: localize('terminal.integrated.defaultProfile.windows', "The default profile used on Windows. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.windows#`', '`#terminal.integrated.shellArgs.windows#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.UseWslProfiles]: {
|
||||
description: localize('terminal.integrated.useWslProfiles', 'Controls whether or not WSL distros are shown in the terminal dropdown'),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
[TerminalSettingId.AllowWorkspaceConfiguration]: {
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
description: localize('terminal.integrated.allowWorkspaceConfiguration', "Allows shell and profile settings to be pick up from a workspace."),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
[TerminalSettingId.InheritEnv]: {
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers terminal configurations required by shared process and remote server.
|
||||
*/
|
||||
export function registerTerminalPlatformConfiguration() {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration(terminalPlatformConfiguration);
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants, TerminalShellType, ITerminalProfile, IRequestResolveVariablesEvent, SafeConfigProvider, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
|
@ -14,6 +14,9 @@ import { Emitter } from 'vs/base/common/event';
|
|||
import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc';
|
||||
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { detectAvailableProfiles } from 'vs/platform/terminal/node/terminalProfiles';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
|
||||
|
||||
enum Constants {
|
||||
MaxRestarts = 5
|
||||
|
@ -25,6 +28,8 @@ enum Constants {
|
|||
*/
|
||||
let lastPtyId = 0;
|
||||
|
||||
let lastResolveVariablesRequestId = 0;
|
||||
|
||||
/**
|
||||
* This service implements IPtyService by launching a pty host process, forwarding messages to and
|
||||
* from the pty host process and manages the connection.
|
||||
|
@ -51,6 +56,9 @@ export class PtyHostService extends Disposable implements IPtyService {
|
|||
readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event;
|
||||
private readonly _onPtyHostResponsive = this._register(new Emitter<void>());
|
||||
readonly onPtyHostResponsive = this._onPtyHostResponsive.event;
|
||||
private readonly _onPtyHostRequestResolveVariables = this._register(new Emitter<IRequestResolveVariablesEvent>());
|
||||
readonly onPtyHostRequestResolveVariables = this._onPtyHostRequestResolveVariables.event;
|
||||
|
||||
private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>());
|
||||
readonly onProcessData = this._onProcessData.event;
|
||||
private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>());
|
||||
|
@ -71,11 +79,16 @@ export class PtyHostService extends Disposable implements IPtyService {
|
|||
readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Platform configuration is required on the process running the pty host (shared process or
|
||||
// remote server).
|
||||
registerTerminalPlatformConfiguration();
|
||||
|
||||
this._register(toDisposable(() => this._disposePtyHost()));
|
||||
|
||||
[this._client, this._proxy] = this._startPtyHost();
|
||||
|
@ -206,6 +219,9 @@ export class PtyHostService extends Disposable implements IPtyService {
|
|||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> {
|
||||
return this._proxy.getDefaultSystemShell(osOverride);
|
||||
}
|
||||
async getProfiles(includeDetectedProfiles: boolean = false): Promise<ITerminalProfile[]> {
|
||||
return detectAvailableProfiles(includeDetectedProfiles, this._buildSafeConfigProvider(), undefined, this._logService, this._resolveVariables.bind(this));
|
||||
}
|
||||
getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return this._proxy.getEnvironment();
|
||||
}
|
||||
|
@ -289,4 +305,33 @@ export class PtyHostService extends Disposable implements IPtyService {
|
|||
this._heartbeatSecondTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _pendingResolveVariablesRequests: Map<number, (resolved: string[]) => void> = new Map();
|
||||
private _resolveVariables(text: string[]): Promise<string[]> {
|
||||
return new Promise<string[]>(resolve => {
|
||||
const id = ++lastResolveVariablesRequestId;
|
||||
this._pendingResolveVariablesRequests.set(id, resolve);
|
||||
this._onPtyHostRequestResolveVariables.fire({ id, originalText: text });
|
||||
});
|
||||
}
|
||||
async acceptPtyHostResolvedVariables(id: number, resolved: string[]) {
|
||||
const request = this._pendingResolveVariablesRequests.get(id);
|
||||
if (request) {
|
||||
request(resolved);
|
||||
this._pendingResolveVariablesRequests.delete(id);
|
||||
} else {
|
||||
this._logService.warn(`Resolved variables received without matching request ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _buildSafeConfigProvider(): SafeConfigProvider {
|
||||
return (key: string) => {
|
||||
const isWorkspaceConfigAllowed = this._configurationService.getValue(TerminalSettingId.AllowWorkspaceConfiguration);
|
||||
if (isWorkspaceConfigAllowed) {
|
||||
return this._configurationService.getValue(key) as any;
|
||||
}
|
||||
const inspected = this._configurationService.inspect(key);
|
||||
return inspected?.userValue || inspected?.defaultValue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,48 +7,58 @@ import * as fs from 'fs';
|
|||
import { normalize, basename, delimiter } from 'vs/base/common/path';
|
||||
import { enumeratePowerShellInstallations } from 'vs/base/node/powershell';
|
||||
import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
|
||||
import { ITerminalProfile, ITerminalProfileObject, ProfileSource, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import * as cp from 'child_process';
|
||||
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { ITerminalEnvironment, SafeConfigProvider } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalEnvironment, ITerminalProfile, ITerminalProfileObject, ProfileSource, SafeConfigProvider, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
|
||||
let profileSources: Map<string, IPotentialTerminalProfile> | undefined;
|
||||
|
||||
export function detectAvailableProfiles(configuredProfilesOnly: boolean, safeConfigProvider: SafeConfigProvider, fsProvider?: IFsProvider, logService?: ILogService, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder, testPaths?: string[]): Promise<ITerminalProfile[]> {
|
||||
export function detectAvailableProfiles(
|
||||
includeDetectedProfiles: boolean,
|
||||
safeConfigProvider: SafeConfigProvider,
|
||||
fsProvider?: IFsProvider,
|
||||
logService?: ILogService,
|
||||
variableResolver?: (text: string[]) => Promise<string[]>,
|
||||
testPaths?: string[]
|
||||
): Promise<ITerminalProfile[]> {
|
||||
fsProvider = fsProvider || {
|
||||
existsFile: pfs.SymlinkSupport.existsFile,
|
||||
readFile: fs.promises.readFile
|
||||
};
|
||||
if (isWindows) {
|
||||
return detectAvailableWindowsProfiles(
|
||||
configuredProfilesOnly,
|
||||
includeDetectedProfiles,
|
||||
fsProvider,
|
||||
logService,
|
||||
safeConfigProvider(TerminalSettingId.UseWslProfiles) || true,
|
||||
safeConfigProvider(TerminalSettingId.ProfilesWindows),
|
||||
safeConfigProvider(TerminalSettingId.DefaultProfileWindows),
|
||||
variableResolver,
|
||||
workspaceFolder
|
||||
variableResolver
|
||||
);
|
||||
}
|
||||
return detectAvailableUnixProfiles(
|
||||
fsProvider,
|
||||
logService,
|
||||
configuredProfilesOnly,
|
||||
includeDetectedProfiles,
|
||||
safeConfigProvider(isMacintosh ? TerminalSettingId.ProfilesMacOs : TerminalSettingId.ProfilesLinux),
|
||||
safeConfigProvider(isMacintosh ? TerminalSettingId.DefaultProfileMacOs : TerminalSettingId.DefaultProfileLinux),
|
||||
testPaths,
|
||||
variableResolver,
|
||||
workspaceFolder
|
||||
variableResolver
|
||||
);
|
||||
}
|
||||
|
||||
async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, fsProvider: IFsProvider, logService?: ILogService, useWslProfiles?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, defaultProfileName?: string, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise<ITerminalProfile[]> {
|
||||
async function detectAvailableWindowsProfiles(
|
||||
includeDetectedProfiles: boolean,
|
||||
fsProvider: IFsProvider,
|
||||
logService?: ILogService,
|
||||
useWslProfiles?: boolean,
|
||||
configProfiles?: { [key: string]: ITerminalProfileObject },
|
||||
defaultProfileName?: string,
|
||||
variableResolver?: (text: string[]) => Promise<string[]>
|
||||
): Promise<ITerminalProfile[]> {
|
||||
// Determine the correct System32 path. We want to point to Sysnative
|
||||
// when the 32-bit version of VS Code is running on a 64-bit machine.
|
||||
// The reason for this is because PowerShell's important PSReadline
|
||||
|
@ -67,7 +77,7 @@ async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, f
|
|||
const detectedProfiles: Map<string, ITerminalProfileObject> = new Map();
|
||||
|
||||
// Add auto detected profiles
|
||||
if (!configuredProfilesOnly) {
|
||||
if (includeDetectedProfiles) {
|
||||
detectedProfiles.set('PowerShell', {
|
||||
source: ProfileSource.Pwsh,
|
||||
icon: Codicon.terminalPowershell.id,
|
||||
|
@ -99,9 +109,9 @@ async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, f
|
|||
|
||||
applyConfigProfilesToMap(configProfiles, detectedProfiles);
|
||||
|
||||
const resultProfiles: ITerminalProfile[] = await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, logService, variableResolver, workspaceFolder);
|
||||
const resultProfiles: ITerminalProfile[] = await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, logService, variableResolver);
|
||||
|
||||
if (!configuredProfilesOnly || (configuredProfilesOnly && useWslProfiles)) {
|
||||
if (includeDetectedProfiles || (!includeDetectedProfiles && useWslProfiles)) {
|
||||
try {
|
||||
const result = await getWslProfiles(`${system32Path}\\${useWSLexe ? 'wsl' : 'bash'}.exe`, defaultProfileName);
|
||||
if (result) {
|
||||
|
@ -115,7 +125,13 @@ async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, f
|
|||
return resultProfiles;
|
||||
}
|
||||
|
||||
async function transformToTerminalProfiles(entries: IterableIterator<[string, ITerminalProfileObject]>, defaultProfileName: string | undefined, fsProvider: IFsProvider, logService?: ILogService, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise<ITerminalProfile[]> {
|
||||
async function transformToTerminalProfiles(
|
||||
entries: IterableIterator<[string, ITerminalProfileObject]>,
|
||||
defaultProfileName: string | undefined,
|
||||
fsProvider: IFsProvider,
|
||||
logService?: ILogService,
|
||||
variableResolver?: (text: string[]) => Promise<string[]>
|
||||
): Promise<ITerminalProfile[]> {
|
||||
const resultProfiles: ITerminalProfile[] = [];
|
||||
for (const [profileName, profile] of entries) {
|
||||
if (profile === null) { continue; }
|
||||
|
@ -138,11 +154,7 @@ async function transformToTerminalProfiles(entries: IterableIterator<[string, IT
|
|||
icon = profile.icon;
|
||||
}
|
||||
|
||||
const paths = originalPaths.slice();
|
||||
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
paths[i] = await variableResolver?.resolveAsync(workspaceFolder, paths[i]) || paths[i];
|
||||
}
|
||||
const paths = (await variableResolver?.(originalPaths)) || originalPaths.slice();
|
||||
const validatedProfile = await validateProfilePaths(profileName, defaultProfileName, paths, fsProvider, args, profile.env, profile.overrideName, profile.isAutoDetected, logService);
|
||||
if (validatedProfile) {
|
||||
validatedProfile.isAutoDetected = profile.isAutoDetected;
|
||||
|
@ -242,11 +254,19 @@ async function getWslProfiles(wslPath: string, defaultProfileName: string | unde
|
|||
return profiles;
|
||||
}
|
||||
|
||||
async function detectAvailableUnixProfiles(fsProvider: IFsProvider, logService?: ILogService, configuredProfilesOnly?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, defaultProfileName?: string, testPaths?: string[], variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise<ITerminalProfile[]> {
|
||||
async function detectAvailableUnixProfiles(
|
||||
fsProvider: IFsProvider,
|
||||
logService?: ILogService,
|
||||
includeDetectedProfiles?: boolean,
|
||||
configProfiles?: { [key: string]: ITerminalProfileObject },
|
||||
defaultProfileName?: string,
|
||||
testPaths?: string[],
|
||||
variableResolver?: (text: string[]) => Promise<string[]>
|
||||
): Promise<ITerminalProfile[]> {
|
||||
const detectedProfiles: Map<string, ITerminalProfileObject> = new Map();
|
||||
|
||||
// Add non-quick launch profiles
|
||||
if (!configuredProfilesOnly) {
|
||||
if (includeDetectedProfiles) {
|
||||
const contents = await fsProvider.readFile('/etc/shells', 'utf8');
|
||||
const profiles = testPaths || contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0);
|
||||
const counts: Map<string, number> = new Map();
|
||||
|
@ -264,7 +284,7 @@ async function detectAvailableUnixProfiles(fsProvider: IFsProvider, logService?:
|
|||
|
||||
applyConfigProfilesToMap(configProfiles, detectedProfiles);
|
||||
|
||||
return await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, logService, variableResolver, workspaceFolder);
|
||||
return await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, logService, variableResolver);
|
||||
}
|
||||
|
||||
function applyConfigProfilesToMap(configProfiles: { [key: string]: ITerminalProfileObject } | undefined, profilesMap: Map<string, ITerminalProfileObject>) {
|
||||
|
@ -315,6 +335,10 @@ export interface IFsProvider {
|
|||
readFile(path: string, options: { encoding: BufferEncoding, flag?: string | number } | BufferEncoding): Promise<string>;
|
||||
}
|
||||
|
||||
export interface IProfileVariableResolver {
|
||||
resolve(text: string[]): Promise<string[]>;
|
||||
}
|
||||
|
||||
interface IPotentialTerminalProfile {
|
||||
profileName: string;
|
||||
paths: string[];
|
|
@ -702,9 +702,6 @@ export class MainThreadTask implements MainThreadTaskShape {
|
|||
});
|
||||
});
|
||||
},
|
||||
getDefaultShellAndArgs: (): Promise<{ shell: string, args: string[] | string | undefined }> => {
|
||||
return Promise.resolve(this._proxy.$getDefaultShellAndArgs());
|
||||
},
|
||||
findExecutable: (command: string, cwd?: string, paths?: string[]): Promise<string | undefined> => {
|
||||
return this._proxy.$findExecutable(command, cwd, paths);
|
||||
}
|
||||
|
|
|
@ -16,9 +16,10 @@ import { ITerminalExternalLinkProvider, ITerminalInstance, ITerminalInstanceServ
|
|||
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { IAvailableProfilesRequest as IAvailableProfilesRequest, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileResolverService, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
|
||||
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
|
||||
|
@ -30,11 +31,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
* This comes in play only when dealing with terminals created on the extension host side
|
||||
*/
|
||||
private _extHostTerminalIds = new Map<string, number>();
|
||||
private _remoteAuthority: string | null;
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
||||
private _dataEventTracker: TerminalDataEventTracker | undefined;
|
||||
private _extHostKind: ExtensionHostKind;
|
||||
/**
|
||||
* A single shared terminal link provider for the exthost. When an ext registers a link
|
||||
* provider, this is registered with the terminal on the renderer side and all links are
|
||||
|
@ -43,17 +42,19 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
*/
|
||||
private _linkProvider: IDisposable | undefined;
|
||||
|
||||
private _os: OperatingSystem = OS;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
private readonly _extHostContext: IExtHostContext,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
|
||||
this._remoteAuthority = extHostContext.remoteAuthority;
|
||||
this._proxy = _extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
|
||||
|
||||
// ITerminalService listeners
|
||||
this._toDispose.add(_terminalService.onInstanceCreated((instance) => {
|
||||
|
@ -61,8 +62,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
this._onInstanceDimensionsChanged(instance);
|
||||
}));
|
||||
|
||||
this._extHostKind = extHostContext.extensionHostKind;
|
||||
|
||||
this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
|
||||
this._toDispose.add(_terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
|
||||
this._toDispose.add(_terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
|
||||
|
@ -70,7 +69,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
this._toDispose.add(_terminalService.onInstanceRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e)));
|
||||
this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null)));
|
||||
this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
|
||||
this._toDispose.add(_terminalService.onRequestAvailableProfiles(e => this._onRequestAvailableProfiles(e)));
|
||||
|
||||
// Set initial ext host state
|
||||
this._terminalService.terminalInstances.forEach(t => {
|
||||
|
@ -89,7 +87,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
this._proxy.$initEnvironmentVariableCollections(serializedCollections);
|
||||
}
|
||||
|
||||
this._terminalService.extHostReady(extHostContext.remoteAuthority!); // TODO@Tyriar: remove null assertion
|
||||
remoteAgentService.getEnvironment().then(async env => {
|
||||
this._os = env?.os || OS;
|
||||
this._updateDefaultProfile();
|
||||
});
|
||||
this._terminalService.onDidChangeAvailableProfiles(() => this._updateDefaultProfile());
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
@ -97,6 +99,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
this._linkProvider?.dispose();
|
||||
}
|
||||
|
||||
private async _updateDefaultProfile() {
|
||||
const remoteAuthority = withNullAsUndefined(this._extHostContext.remoteAuthority);
|
||||
const defaultProfile = this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority, os: this._os });
|
||||
const defaultAutomationProfile = this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority, os: this._os, allowAutomationShell: true });
|
||||
this._proxy.$acceptDefaultProfile(...await Promise.all([defaultProfile, defaultAutomationProfile]));
|
||||
}
|
||||
|
||||
private _getTerminalId(id: TerminalIdentifier): number | undefined {
|
||||
if (typeof id === 'number') {
|
||||
return id;
|
||||
|
@ -311,21 +320,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
this._getTerminalProcess(terminalId)?.emitLatency(sum / COUNT);
|
||||
}
|
||||
|
||||
private _isPrimaryExtHost(): boolean {
|
||||
// The "primary" ext host is the remote ext host if there is one, otherwise the local
|
||||
const conn = this._remoteAgentService.getConnection();
|
||||
if (conn) {
|
||||
return this._remoteAuthority === conn.remoteAuthority;
|
||||
}
|
||||
return this._extHostKind !== ExtensionHostKind.LocalWebWorker;
|
||||
}
|
||||
|
||||
private async _onRequestAvailableProfiles(req: IAvailableProfilesRequest): Promise<void> {
|
||||
if (this._isPrimaryExtHost()) {
|
||||
req.callback(await this._proxy.$getAvailableProfiles(req.configuredProfilesOnly));
|
||||
}
|
||||
}
|
||||
|
||||
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy | undefined {
|
||||
const terminal = this._terminalProcessProxies.get(terminalId);
|
||||
if (!terminal) {
|
||||
|
|
|
@ -296,7 +296,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
get uriScheme() { return initData.environment.appUriScheme; },
|
||||
get clipboard(): vscode.Clipboard { return extHostClipboard.value; },
|
||||
get shell() {
|
||||
return extHostTerminalService.getDefaultShell(false, configProvider);
|
||||
return extHostTerminalService.getDefaultShell(false);
|
||||
},
|
||||
get isTelemetryEnabled() {
|
||||
return extHostTelemetry.getTelemetryEnabled();
|
||||
|
|
|
@ -39,7 +39,7 @@ import { IRemoteConnectionData, RemoteAuthorityResolverErrorCode, ResolverResult
|
|||
import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel';
|
||||
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
|
||||
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile } from 'vs/platform/terminal/common/terminal';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
|
@ -56,7 +56,6 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
|
|||
import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtensionRunTestsRequest, InternalTestItem, ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, RunTestForProviderRequest, RunTestsRequest, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { InternalTimelineOptions, Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { ActivationKind, ExtensionHostKind, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
@ -1668,11 +1667,6 @@ export interface ExtHostTelemetryShape {
|
|||
$onDidChangeTelemetryEnabled(enabled: boolean): void;
|
||||
}
|
||||
|
||||
export interface IShellAndArgsDto {
|
||||
shell: string;
|
||||
args: string[] | string | undefined;
|
||||
}
|
||||
|
||||
export interface ITerminalLinkDto {
|
||||
/** The ID of the link to enable activation and disposal. */
|
||||
id: number;
|
||||
|
@ -1706,10 +1700,10 @@ export interface ExtHostTerminalServiceShape {
|
|||
$acceptProcessRequestInitialCwd(id: number): void;
|
||||
$acceptProcessRequestCwd(id: number): void;
|
||||
$acceptProcessRequestLatency(id: number): number;
|
||||
$getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]>;
|
||||
$provideLinks(id: number, line: string): Promise<ITerminalLinkDto[]>;
|
||||
$activateLink(id: number, linkId: number): void;
|
||||
$initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void;
|
||||
$acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void;
|
||||
}
|
||||
|
||||
export interface ExtHostSCMShape {
|
||||
|
@ -1728,7 +1722,6 @@ export interface ExtHostTaskShape {
|
|||
$onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): void;
|
||||
$OnDidEndTask(execution: tasks.TaskExecutionDTO): void;
|
||||
$resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string; }, variables: string[]; }): Promise<{ process?: string; variables: { [key: string]: string; }; }>;
|
||||
$getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined; }>;
|
||||
$jsonTasksSupported(): Thenable<boolean>;
|
||||
$findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string | undefined>;
|
||||
}
|
||||
|
|
|
@ -605,8 +605,6 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
|
|||
|
||||
public abstract $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }>;
|
||||
|
||||
public abstract $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>;
|
||||
|
||||
private nextHandle(): number {
|
||||
return this._handleCounter++;
|
||||
}
|
||||
|
@ -775,10 +773,6 @@ export class WorkerExtHostTask extends ExtHostTaskBase {
|
|||
return result;
|
||||
}
|
||||
|
||||
public $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async $jsonTasksSupported(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
|
||||
import type * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
|
@ -19,9 +18,8 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter
|
|||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
|
||||
|
||||
|
@ -40,11 +38,10 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
|
|||
createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal;
|
||||
createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal;
|
||||
attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void;
|
||||
getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
|
||||
getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
|
||||
getDefaultShell(useAutomationShell: boolean): string;
|
||||
getDefaultShellArgs(useAutomationShell: boolean): string[] | string;
|
||||
registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable;
|
||||
getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection;
|
||||
getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
|
||||
}
|
||||
|
||||
export const IExtHostTerminalService = createDecorator<IExtHostTerminalService>('IExtHostTerminalService');
|
||||
|
@ -293,6 +290,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
|||
protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {};
|
||||
protected _getTerminalPromises: { [id: number]: Promise<ExtHostTerminal | undefined> } = {};
|
||||
protected _environmentVariableCollections: Map<string, EnvironmentVariableCollection> = new Map();
|
||||
private _defaultProfile: ITerminalProfile | undefined;
|
||||
private _defaultAutomationProfile: ITerminalProfile | undefined;
|
||||
|
||||
private readonly _bufferer: TerminalDataBufferer;
|
||||
private readonly _linkProviders: Set<vscode.TerminalLinkProvider> = new Set();
|
||||
|
@ -336,10 +335,15 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
|||
|
||||
public abstract createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal;
|
||||
public abstract createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal;
|
||||
public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
|
||||
public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
|
||||
public abstract getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
|
||||
public abstract $getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]>;
|
||||
public getDefaultShell(useAutomationShell: boolean): string {
|
||||
const profile = useAutomationShell ? this._defaultAutomationProfile : this._defaultProfile;
|
||||
return profile?.path || '';
|
||||
}
|
||||
|
||||
public getDefaultShellArgs(useAutomationShell: boolean): string[] | string {
|
||||
const profile = useAutomationShell ? this._defaultAutomationProfile : this._defaultProfile;
|
||||
return profile?.args || [''];
|
||||
}
|
||||
|
||||
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
|
@ -690,6 +694,11 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
|||
});
|
||||
}
|
||||
|
||||
public $acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void {
|
||||
this._defaultProfile = profile;
|
||||
this._defaultAutomationProfile = automationProfile;
|
||||
}
|
||||
|
||||
private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
|
||||
this._environmentVariableCollections.set(extensionIdentifier, collection);
|
||||
collection.onDidChangeCollection(() => {
|
||||
|
@ -778,20 +787,4 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService {
|
|||
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
public async getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
public $getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]> {
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,8 +81,8 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
|||
}
|
||||
|
||||
const configProvider = await this._configurationService.getConfigProvider();
|
||||
const shell = this._terminalService.getDefaultShell(true, configProvider);
|
||||
const shellArgs = this._terminalService.getDefaultShellArgs(true, configProvider);
|
||||
const shell = this._terminalService.getDefaultShell(true);
|
||||
const shellArgs = this._terminalService.getDefaultShellArgs(true);
|
||||
|
||||
const shellConfig = JSON.stringify({ shell, shellArgs });
|
||||
let terminal = await this._integratedTerminalInstances.checkout(shellConfig);
|
||||
|
|
|
@ -174,10 +174,6 @@ export class ExtHostTask extends ExtHostTaskBase {
|
|||
return result;
|
||||
}
|
||||
|
||||
public $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> {
|
||||
return this._terminalService.getDefaultShellAndArgs(true);
|
||||
}
|
||||
|
||||
public async $jsonTasksSupported(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,52 +3,18 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { SafeConfigProvider } from 'vs/platform/terminal/common/terminal';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfigProvider, ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
|
||||
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { ITerminalProfile, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { detectAvailableProfiles } from 'vs/workbench/contrib/terminal/node/terminalProfiles';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
private _variableResolver: ExtHostVariableResolverService | undefined;
|
||||
private _variableResolverPromise: Promise<ExtHostVariableResolverService>;
|
||||
private _lastActiveWorkspace: IWorkspaceFolder | undefined;
|
||||
|
||||
private _defaultShell: string | undefined;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@IExtHostConfiguration private _extHostConfiguration: ExtHostConfiguration,
|
||||
@IExtHostWorkspace private _extHostWorkspace: ExtHostWorkspace,
|
||||
@IExtHostDocumentsAndEditors private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
@ILogService private _logService: ILogService,
|
||||
@IExtHostEditorTabs private _extHostEditorTabs: IExtHostEditorTabs
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService
|
||||
) {
|
||||
super(true, extHostRpc);
|
||||
|
||||
// Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous
|
||||
// and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are
|
||||
// starting up but if not, we run getSystemShellSync below which gets a sane default.
|
||||
getSystemShell(platform.OS, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s);
|
||||
|
||||
this._updateLastActiveWorkspace();
|
||||
this._variableResolverPromise = this._updateVariableResolver();
|
||||
this._registerListeners();
|
||||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
|
||||
|
@ -76,72 +42,4 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
|||
);
|
||||
return terminal.value;
|
||||
}
|
||||
|
||||
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {
|
||||
return terminalEnvironment.getDefaultShell(
|
||||
this._buildSafeConfigProvider(configProvider),
|
||||
this._defaultShell ?? getSystemShellSync(platform.OS, process.env as platform.IProcessEnvironment),
|
||||
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
|
||||
process.env.windir,
|
||||
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, process.env, this._variableResolver),
|
||||
this._logService,
|
||||
useAutomationShell
|
||||
);
|
||||
}
|
||||
|
||||
public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string {
|
||||
return terminalEnvironment.getDefaultShellArgs(
|
||||
this._buildSafeConfigProvider(configProvider),
|
||||
useAutomationShell,
|
||||
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, process.env, this._variableResolver),
|
||||
this._logService
|
||||
);
|
||||
}
|
||||
|
||||
private _registerListeners(): void {
|
||||
this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(() => this._updateLastActiveWorkspace());
|
||||
this._extHostWorkspace.onDidChangeWorkspace(() => {
|
||||
this._variableResolverPromise = this._updateVariableResolver();
|
||||
});
|
||||
}
|
||||
|
||||
private _updateLastActiveWorkspace(): void {
|
||||
const activeEditor = this._extHostDocumentsAndEditors.activeEditor();
|
||||
if (activeEditor) {
|
||||
this._lastActiveWorkspace = this._extHostWorkspace.getWorkspaceFolder(activeEditor.document.uri) as IWorkspaceFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateVariableResolver(): Promise<ExtHostVariableResolverService> {
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2();
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider, this._extHostEditorTabs);
|
||||
return this._variableResolver;
|
||||
}
|
||||
|
||||
public async $getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]> {
|
||||
const safeConfigProvider = this._buildSafeConfigProvider(await this._extHostConfiguration.getConfigProvider());
|
||||
return detectAvailableProfiles(configuredProfilesOnly, safeConfigProvider, undefined, this._logService, await this._variableResolverPromise, this._lastActiveWorkspace);
|
||||
}
|
||||
|
||||
public async getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
return {
|
||||
shell: this.getDefaultShell(useAutomationShell, configProvider),
|
||||
args: this.getDefaultShellArgs(useAutomationShell, configProvider)
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Remove when workspace trust is enabled
|
||||
private _buildSafeConfigProvider(configProvider: ExtHostConfigProvider): SafeConfigProvider {
|
||||
const config = configProvider.getConfiguration();
|
||||
return (key: string) => {
|
||||
const isWorkspaceConfigAllowed = config.get(TerminalSettingId.AllowWorkspaceConfiguration);
|
||||
if (isWorkspaceConfigAllowed) {
|
||||
return config.get(key) as any;
|
||||
}
|
||||
const inspected = config.inspect(key);
|
||||
return inspected?.globalValue || inspected?.defaultValue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import Constants from 'vs/workbench/contrib/markers/browser/constants';
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { ITerminalProfileResolverService, TerminalSettingId, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors';
|
||||
|
@ -47,7 +47,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
|||
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
|
||||
import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus';
|
||||
import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
|
||||
|
@ -1019,20 +1019,15 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
|||
const description = nls.localize('TerminalTaskSystem.terminalDescription', 'Task');
|
||||
let originalCommand = task.command.name;
|
||||
if (isShellCommand) {
|
||||
let defaultConfig: { shell: string, args: string[] | string | undefined };
|
||||
if (variableResolver.taskSystemInfo) {
|
||||
defaultConfig = await variableResolver.taskSystemInfo.getDefaultShellAndArgs();
|
||||
} else {
|
||||
const defaultProfile = await this.terminalProfileResolverService.getDefaultProfile({
|
||||
allowAutomationShell: true,
|
||||
os: Platform.OS,
|
||||
remoteAuthority: this.environmentService.remoteAuthority
|
||||
});
|
||||
defaultConfig = {
|
||||
shell: defaultProfile.path,
|
||||
args: defaultProfile.args
|
||||
};
|
||||
}
|
||||
const defaultProfile = await this.terminalProfileResolverService.getDefaultProfile({
|
||||
allowAutomationShell: true,
|
||||
os: Platform.OS,
|
||||
remoteAuthority: this.environmentService.remoteAuthority
|
||||
});
|
||||
const defaultConfig = {
|
||||
shell: defaultProfile.path,
|
||||
args: defaultProfile.args
|
||||
};
|
||||
shellLaunchConfig = { name: terminalName, description, executable: defaultConfig.shell, args: defaultConfig.args, waitOnExit };
|
||||
let shellSpecified: boolean = false;
|
||||
let shellOptions: ShellConfiguration | undefined = task.command.options && task.command.options.shell;
|
||||
|
@ -1043,10 +1038,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
|||
}
|
||||
if (shellOptions.args) {
|
||||
shellLaunchConfig.args = await this.resolveVariables(variableResolver, shellOptions.args.slice());
|
||||
} else {
|
||||
shellLaunchConfig.args = [];
|
||||
}
|
||||
}
|
||||
if (shellLaunchConfig.args === undefined) {
|
||||
shellLaunchConfig.args = [];
|
||||
}
|
||||
let shellArgs = Array.isArray(shellLaunchConfig.args!) ? <string[]>shellLaunchConfig.args!.slice(0) : [shellLaunchConfig.args!];
|
||||
let toAdd: string[] = [];
|
||||
let commandLine = this.buildShellCommandLine(platform, shellLaunchConfig.executable!, shellOptions, command, originalCommand, args);
|
||||
|
|
|
@ -120,7 +120,6 @@ export interface TaskSystemInfo {
|
|||
context: any;
|
||||
uriProvider: (this: void, path: string) => URI;
|
||||
resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise<ResolvedVariables | undefined>;
|
||||
getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>;
|
||||
findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
@ -14,11 +16,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationHandle, INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalsLayoutInfo, ITerminalsLayoutInfoById } from 'vs/platform/terminal/common/terminal';
|
||||
import { IRequestResolveVariablesEvent, IShellLaunchConfig, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById } from 'vs/platform/terminal/common/terminal';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { RemotePty } from 'vs/workbench/contrib/terminal/browser/remotePty';
|
||||
import { IRemoteTerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ICompleteTerminalConfiguration, RemoteTerminalChannelClient, REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
|
||||
import { IRemoteTerminalAttachTarget, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
export class RemoteTerminalService extends Disposable implements IRemoteTerminalService {
|
||||
|
@ -34,6 +39,8 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal
|
|||
readonly onPtyHostResponsive = this._onPtyHostResponsive.event;
|
||||
private readonly _onPtyHostRestart = this._register(new Emitter<void>());
|
||||
readonly onPtyHostRestart = this._onPtyHostRestart.event;
|
||||
private readonly _onPtyHostRequestResolveVariables = this._register(new Emitter<IRequestResolveVariablesEvent>());
|
||||
readonly onPtyHostRequestResolveVariables = this._onPtyHostRequestResolveVariables.event;
|
||||
|
||||
constructor(
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
|
@ -41,7 +48,10 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal
|
|||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
|
||||
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
|
||||
@IHistoryService historyService: IHistoryService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -122,6 +132,15 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal
|
|||
this._onPtyHostResponsive.fire();
|
||||
}));
|
||||
}
|
||||
this._register(channel.onPtyHostRequestResolveVariables(async e => {
|
||||
const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file);
|
||||
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
|
||||
const resolveCalls: Promise<string>[] = e.originalText.map(t => {
|
||||
return configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, t);
|
||||
});
|
||||
const result = await Promise.all(resolveCalls);
|
||||
channel.acceptPtyHostResolvedVariables(e.id, result);
|
||||
}));
|
||||
} else {
|
||||
this._remoteTerminalChannel = null;
|
||||
}
|
||||
|
@ -204,6 +223,10 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal
|
|||
return this._remoteTerminalChannel?.getDefaultSystemShell(osOverride) || '';
|
||||
}
|
||||
|
||||
async getProfiles(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]> {
|
||||
return this._remoteTerminalChannel?.getProfiles(includeDetectedProfiles) || [];
|
||||
}
|
||||
|
||||
async getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return this._remoteTerminalChannel?.getEnvironment() || {};
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminal
|
|||
import { KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, TERMINAL_VIEW_ID, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||
import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IRemoteTerminalService, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
|
@ -29,7 +28,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
|||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
|
||||
import { terminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { registerTerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { terminalViewIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
|
||||
import { RemoteTerminalService } from 'vs/workbench/contrib/terminal/browser/remoteTerminalService';
|
||||
|
@ -37,6 +36,7 @@ import { WindowsShellType } from 'vs/platform/terminal/common/terminal';
|
|||
import { isIOS, isWindows } from 'vs/base/common/platform';
|
||||
import { setupTerminalMenus } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
|
||||
import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService';
|
||||
import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
|
||||
|
||||
// Register services
|
||||
registerSingleton(ITerminalService, TerminalService, true);
|
||||
|
@ -59,8 +59,8 @@ const quickAccessNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpe
|
|||
CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigatePreviousInTerminalPickerId, false) });
|
||||
|
||||
// Register configurations
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration(terminalConfiguration);
|
||||
registerTerminalPlatformConfiguration();
|
||||
registerTerminalConfiguration();
|
||||
|
||||
// Register views
|
||||
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
|
|
|
@ -8,8 +8,8 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IOffProcessTerminalService, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalLaunchError, ITerminalTabLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IAvailableProfilesRequest, ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, ITerminalProfile, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IOffProcessTerminalService, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import type { Terminal as XTermTerminal } from 'xterm';
|
||||
import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
|
||||
import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11';
|
||||
|
@ -94,6 +94,7 @@ export interface ITerminalService {
|
|||
isProcessSupportRegistered: boolean;
|
||||
readonly connectionState: TerminalConnectionState;
|
||||
readonly availableProfiles: ITerminalProfile[];
|
||||
readonly profilesReady: Promise<void>;
|
||||
|
||||
initializeTerminals(): Promise<void>;
|
||||
onActiveGroupChanged: Event<void>;
|
||||
|
@ -112,7 +113,6 @@ export interface ITerminalService {
|
|||
onInstanceIconChanged: Event<ITerminalInstance | undefined>;
|
||||
onInstancePrimaryStatusChanged: Event<ITerminalInstance>;
|
||||
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
|
||||
onRequestAvailableProfiles: Event<IAvailableProfilesRequest>;
|
||||
onDidRegisterProcessSupport: Event<void>;
|
||||
onDidChangeConnectionState: Event<void>;
|
||||
onDidChangeAvailableProfiles: Event<ITerminalProfile[]>;
|
||||
|
@ -187,7 +187,6 @@ export interface ITerminalService {
|
|||
|
||||
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
|
||||
|
||||
extHostReady(remoteAuthority: string): void;
|
||||
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise<ITerminalLaunchError | undefined>;
|
||||
isAttachedToTerminal(remoteTerm: IRemoteTerminalAttachTarget): boolean;
|
||||
}
|
||||
|
|
|
@ -26,13 +26,13 @@ import { IListService } from 'vs/platform/list/browser/listService';
|
|||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ILocalTerminalService } from 'vs/platform/terminal/common/terminal';
|
||||
import { ILocalTerminalService, ITerminalProfile, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||
import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions';
|
||||
import { Direction, IRemoteTerminalService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
|
||||
import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, ITerminalProfile, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TerminalCommandId, TerminalSettingId, TERMINAL_VIEW_ID, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TerminalCommandId, TERMINAL_VIEW_ID, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
|
|
|
@ -24,7 +24,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB
|
|||
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
|
||||
import { ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, SUGGESTED_RENDERER_TYPE, ITerminalProfileResolverService, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, SUGGESTED_RENDERER_TYPE, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ansiColorIdentifiers, ansiColorMap, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
|
||||
|
@ -46,7 +46,7 @@ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTy
|
|||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
|
||||
import { AutoOpenBarrier } from 'vs/base/common/async';
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
import { localize } from 'vs/nls';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyAndExpr, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, TerminalCommandId, TerminalSettingId, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, TerminalCommandId, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
const enum ContextMenuGroup {
|
||||
Create = '1_create',
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { ProcessState, ITerminalProcessManager, ITerminalConfigHelper, IBeforeProcessDataEvent, ITerminalProfileResolverService, ITerminalConfiguration, TERMINAL_CONFIG_SECTION, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ProcessState, ITerminalProcessManager, ITerminalConfigHelper, IBeforeProcessDataEvent, ITerminalProfileResolverService, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
|
@ -22,7 +22,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
|
|||
import { EnvironmentVariableInfoChangesActive, EnvironmentVariableInfoStale } from 'vs/workbench/contrib/terminal/browser/environmentVariableInfo';
|
||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||
import { IEnvironmentVariableInfo, IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, TerminalShellType, ILocalTerminalService, IOffProcessTerminalService, ITerminalDimensions } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, TerminalShellType, ILocalTerminalService, IOffProcessTerminalService, ITerminalDimensions, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder';
|
||||
import { localize } from 'vs/nls';
|
||||
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
|
||||
|
@ -172,9 +172,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
|||
}
|
||||
|
||||
detachFromProcess(): void {
|
||||
if (this._process?.detach) {
|
||||
this._process.detach();
|
||||
}
|
||||
this._process?.detach?.();
|
||||
}
|
||||
|
||||
async createProcess(
|
||||
|
|
|
@ -13,8 +13,8 @@ import { IRemoteTerminalService, ITerminalService } from 'vs/workbench/contrib/t
|
|||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IProcessEnvironment, OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfigResolveOptions, ITerminalProfile, ITerminalProfileResolverService, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, ITerminalProfile, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
@ -153,7 +153,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
|
|||
return shellSettingProfile;
|
||||
}
|
||||
|
||||
// Return the real default profile if it exists and is valid
|
||||
// Return the real default profile if it exists and is valid, wait for profiles to be ready
|
||||
// if the window just opened
|
||||
await this._terminalService.profilesReady;
|
||||
const defaultProfile = this._getUnresolvedRealDefaultProfile(options.os);
|
||||
if (defaultProfile) {
|
||||
return defaultProfile;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { AutoOpenBarrier, timeout } from 'vs/base/common/async';
|
||||
import { debounce, throttle } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
@ -17,7 +17,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
|||
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById } from 'vs/platform/terminal/common/terminal';
|
||||
import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
|
@ -25,9 +25,8 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term
|
|||
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
|
||||
import { TerminalGroup } from 'vs/workbench/contrib/terminal/browser/terminalGroup';
|
||||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
import { IAvailableProfilesRequest, IRemoteTerminalAttachTarget, ITerminalProfile, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, TERMINAL_VIEW_ID, ITerminalProfileObject, KEYBINDING_CONTEXT_TERMINAL_COUNT, TerminalSettingId, ITerminalTypeContribution, KEYBINDING_CONTEXT_TERMINAL_TABS_MOUSE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, TERMINAL_VIEW_ID, KEYBINDING_CONTEXT_TERMINAL_COUNT, ITerminalTypeContribution, KEYBINDING_CONTEXT_TERMINAL_TABS_MOUSE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService, ShutdownReason, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
@ -53,7 +52,6 @@ export class TerminalService implements ITerminalService {
|
|||
private _terminalGroups: ITerminalGroup[] = [];
|
||||
private _backgroundedTerminalInstances: ITerminalInstance[] = [];
|
||||
private _findState: FindReplaceState;
|
||||
private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {};
|
||||
private _activeGroupIndex: number;
|
||||
private _activeInstanceIndex: number;
|
||||
private _linkProviders: Set<ITerminalExternalLinkProvider> = new Set();
|
||||
|
@ -61,6 +59,7 @@ export class TerminalService implements ITerminalService {
|
|||
private _processSupportContextKey: IContextKey<boolean>;
|
||||
private readonly _localTerminalService?: ILocalTerminalService;
|
||||
private readonly _offProcessTerminalService?: IOffProcessTerminalService;
|
||||
private _profilesReadyBarrier: AutoOpenBarrier;
|
||||
private _availableProfiles: ITerminalProfile[] | undefined;
|
||||
private _configHelper: TerminalConfigHelper;
|
||||
private _terminalContainer: HTMLElement | undefined;
|
||||
|
@ -72,6 +71,7 @@ export class TerminalService implements ITerminalService {
|
|||
public get terminalGroups(): ITerminalGroup[] { return this._terminalGroups; }
|
||||
public get isProcessSupportRegistered(): boolean { return !!this._processSupportContextKey.get(); }
|
||||
get connectionState(): TerminalConnectionState { return this._connectionState; }
|
||||
get profilesReady(): Promise<void> { return this._profilesReadyBarrier.wait().then(() => { }); }
|
||||
get availableProfiles(): ITerminalProfile[] {
|
||||
this._refreshAvailableProfiles();
|
||||
return this._availableProfiles || [];
|
||||
|
@ -114,8 +114,6 @@ export class TerminalService implements ITerminalService {
|
|||
public get onInstancePrimaryStatusChanged(): Event<ITerminalInstance> { return this._onInstancePrimaryStatusChanged.event; }
|
||||
private readonly _onGroupDisposed = new Emitter<ITerminalGroup>();
|
||||
public get onGroupDisposed(): Event<ITerminalGroup> { return this._onGroupDisposed.event; }
|
||||
private readonly _onRequestAvailableProfiles = new Emitter<IAvailableProfilesRequest>();
|
||||
get onRequestAvailableProfiles(): Event<IAvailableProfilesRequest> { return this._onRequestAvailableProfiles.event; }
|
||||
private readonly _onDidRegisterProcessSupport = new Emitter<void>();
|
||||
get onDidRegisterProcessSupport(): Event<void> { return this._onDidRegisterProcessSupport.event; }
|
||||
private readonly _onDidChangeConnectionState = new Emitter<void>();
|
||||
|
@ -140,7 +138,6 @@ export class TerminalService implements ITerminalService {
|
|||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@optional(ILocalTerminalService) localTerminalService: ILocalTerminalService
|
||||
|
@ -194,23 +191,24 @@ export class TerminalService implements ITerminalService {
|
|||
|
||||
const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions;
|
||||
|
||||
const conn = this._remoteAgentService.getConnection();
|
||||
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
|
||||
this._whenExtHostReady(remoteAuthority).then(() => {
|
||||
this._refreshAvailableProfiles();
|
||||
});
|
||||
|
||||
// Connect to the extension host if it's there, set the connection state to connected when
|
||||
// it's done. This should happen even when there is no extension host.
|
||||
this._connectionState = TerminalConnectionState.Connecting;
|
||||
|
||||
const isPersistentRemote = !!this._environmentService.remoteAuthority && enableTerminalReconnection;
|
||||
let initPromise: Promise<any> = isPersistentRemote ? this._remoteTerminalsInitPromise = this._reconnectToRemoteTerminals() :
|
||||
enableTerminalReconnection ? this._localTerminalsInitPromise = this._reconnectToLocalTerminals() :
|
||||
Promise.resolve();
|
||||
this._offProcessTerminalService = isPersistentRemote ? this._remoteTerminalService :
|
||||
enableTerminalReconnection ? this._localTerminalService : undefined;
|
||||
let initPromise: Promise<any> = isPersistentRemote
|
||||
? this._remoteTerminalsInitPromise = this._reconnectToRemoteTerminals()
|
||||
: enableTerminalReconnection
|
||||
? this._localTerminalsInitPromise = this._reconnectToLocalTerminals()
|
||||
: Promise.resolve();
|
||||
this._offProcessTerminalService = !!this._environmentService.remoteAuthority ? this._remoteTerminalService : this._localTerminalService;
|
||||
initPromise.then(() => this._setConnected());
|
||||
|
||||
// Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual
|
||||
// default terminal before launching the first terminal. This isn't expected to ever take
|
||||
// this long.
|
||||
this._profilesReadyBarrier = new AutoOpenBarrier(5000);
|
||||
this._refreshAvailableProfiles();
|
||||
}
|
||||
|
||||
private _setConnected() {
|
||||
|
@ -323,43 +321,22 @@ export class TerminalService implements ITerminalService {
|
|||
});
|
||||
}
|
||||
|
||||
async extHostReady(remoteAuthority: string): Promise<void> {
|
||||
this._createExtHostReadyEntry(remoteAuthority);
|
||||
this._extHostsReady[remoteAuthority]!.resolve();
|
||||
}
|
||||
|
||||
@throttle(10000)
|
||||
@throttle(2000)
|
||||
private async _refreshAvailableProfiles(): Promise<void> {
|
||||
const result = await this._detectProfiles(true);
|
||||
const result = await this._detectProfiles();
|
||||
if (!equals(result, this._availableProfiles)) {
|
||||
this._availableProfiles = result;
|
||||
this._onDidChangeAvailableProfiles.fire(this._availableProfiles);
|
||||
this._profilesReadyBarrier.open();
|
||||
}
|
||||
}
|
||||
|
||||
private async _detectProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]> {
|
||||
await this._extensionService.whenInstalledExtensionsRegistered();
|
||||
// Wait for the remoteAuthority to be ready (and listening for events) before firing
|
||||
// the event to spawn the ext host process
|
||||
const conn = this._remoteAgentService.getConnection();
|
||||
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
|
||||
await this._whenExtHostReady(remoteAuthority);
|
||||
return new Promise(r => this._onRequestAvailableProfiles.fire({ callback: r, configuredProfilesOnly: configuredProfilesOnly }));
|
||||
}
|
||||
|
||||
private async _whenExtHostReady(remoteAuthority: string): Promise<void> {
|
||||
this._createExtHostReadyEntry(remoteAuthority);
|
||||
return this._extHostsReady[remoteAuthority]!.promise;
|
||||
}
|
||||
|
||||
private _createExtHostReadyEntry(remoteAuthority: string): void {
|
||||
if (this._extHostsReady[remoteAuthority]) {
|
||||
return;
|
||||
private async _detectProfiles(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]> {
|
||||
const offProcService = this._offProcessTerminalService;
|
||||
if (!offProcService) {
|
||||
return this._availableProfiles || [];
|
||||
}
|
||||
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>(r => resolve = r);
|
||||
this._extHostsReady[remoteAuthority] = { promise, resolve };
|
||||
return offProcService?.getProfiles(includeDetectedProfiles);
|
||||
}
|
||||
|
||||
private _onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
|
||||
|
@ -413,6 +390,9 @@ export class TerminalService implements ITerminalService {
|
|||
|
||||
@debounce(500)
|
||||
private _saveState(): void {
|
||||
if (!this.configHelper.config.enablePersistentSessions) {
|
||||
return;
|
||||
}
|
||||
const state: ITerminalsLayoutInfoById = {
|
||||
tabs: this.terminalGroups.map(g => g.getLayoutInfo(g === this.getActiveGroup()))
|
||||
};
|
||||
|
@ -421,7 +401,7 @@ export class TerminalService implements ITerminalService {
|
|||
|
||||
@debounce(500)
|
||||
private _updateTitle(instance?: ITerminalInstance): void {
|
||||
if (!instance || !instance.persistentProcessId || !instance.title) {
|
||||
if (!this.configHelper.config.enablePersistentSessions || !instance || !instance.persistentProcessId || !instance.title) {
|
||||
return;
|
||||
}
|
||||
this._offProcessTerminalService?.updateTitle(instance.persistentProcessId, instance.title);
|
||||
|
@ -429,7 +409,7 @@ export class TerminalService implements ITerminalService {
|
|||
|
||||
@debounce(500)
|
||||
private _updateIcon(instance?: ITerminalInstance): void {
|
||||
if (!instance || !instance.persistentProcessId || !instance.icon) {
|
||||
if (!this.configHelper.config.enablePersistentSessions || !instance || !instance.persistentProcessId || !instance.icon) {
|
||||
return;
|
||||
}
|
||||
this._offProcessTerminalService?.updateIcon(instance.persistentProcessId, instance.icon.id, instance.color);
|
||||
|
@ -765,7 +745,7 @@ export class TerminalService implements ITerminalService {
|
|||
|
||||
async showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise<ITerminalInstance | undefined> {
|
||||
let keyMods: IKeyMods | undefined;
|
||||
const profiles = await this._detectProfiles(false);
|
||||
const profiles = await this._detectProfiles(true);
|
||||
const platformKey = await this._getPlatformKey();
|
||||
|
||||
const options: IPickOptions<IProfileQuickPickItem> = {
|
||||
|
@ -1043,11 +1023,6 @@ interface IProfileQuickPickItem extends IQuickPickItem {
|
|||
profile: ITerminalProfile | ITerminalTypeContribution;
|
||||
}
|
||||
|
||||
interface IExtHostReadyEntry {
|
||||
promise: Promise<void>;
|
||||
resolve: () => void;
|
||||
}
|
||||
|
||||
interface IInstanceLocation {
|
||||
group: ITerminalGroup,
|
||||
groupIndex: number,
|
||||
|
|
|
@ -23,7 +23,8 @@ import { Action, IAction, Separator } from 'vs/base/common/actions';
|
|||
import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_TABS_NARROW_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_MOUSE, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_TABS_NARROW_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_MOUSE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, TerminalCommandId, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
|
|
@ -22,7 +22,8 @@ import { IViewDescriptorService } from 'vs/workbench/common/views';
|
|||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { ITerminalProfile, ITerminalProfileResolverService, TerminalCommandId, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalProfileResolverService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalSettingId, ITerminalProfile } from 'vs/platform/terminal/common/terminal';
|
||||
import { ActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
|
||||
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
|
|
|
@ -18,7 +18,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
|||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessDataEvent, IRequestResolveVariablesEvent, IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
|
||||
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
|
||||
|
||||
|
@ -84,6 +84,9 @@ export class RemoteTerminalChannelClient {
|
|||
get onPtyHostResponsive(): Event<void> {
|
||||
return this._channel.listen<void>('$onPtyHostResponsiveEvent');
|
||||
}
|
||||
get onPtyHostRequestResolveVariables(): Event<IRequestResolveVariablesEvent> {
|
||||
return this._channel.listen<IRequestResolveVariablesEvent>('$onPtyHostRequestResolveVariablesEvent');
|
||||
}
|
||||
get onProcessData(): Event<{ id: number, event: IProcessDataEvent | string }> {
|
||||
return this._channel.listen<{ id: number, event: IProcessDataEvent | string }>('$onProcessDataEvent');
|
||||
}
|
||||
|
@ -235,6 +238,12 @@ export class RemoteTerminalChannelClient {
|
|||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> {
|
||||
return this._channel.call('$getDefaultSystemShell', [osOverride]);
|
||||
}
|
||||
getProfiles(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]> {
|
||||
return this._channel.call('$getProfiles', [includeDetectedProfiles]);
|
||||
}
|
||||
acceptPtyHostResolvedVariables(id: number, resolved: string[]) {
|
||||
return this._channel.call('$acceptPtyHostResolvedVariables', [id, resolved]);
|
||||
}
|
||||
|
||||
getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return this._channel.call('$getEnvironment');
|
||||
|
|
|
@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
|||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
|
@ -261,44 +261,6 @@ export interface IBeforeProcessDataEvent {
|
|||
data: string;
|
||||
}
|
||||
|
||||
export interface ITerminalProfile {
|
||||
profileName: string;
|
||||
path: string;
|
||||
isDefault: boolean;
|
||||
isAutoDetected?: boolean;
|
||||
args?: string | string[] | undefined;
|
||||
env?: ITerminalEnvironment;
|
||||
overrideName?: boolean;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export const enum ProfileSource {
|
||||
GitBash = 'Git Bash',
|
||||
Pwsh = 'PowerShell'
|
||||
}
|
||||
|
||||
export interface IBaseUnresolvedTerminalProfile {
|
||||
args?: string | string[] | undefined;
|
||||
isAutoDetected?: boolean;
|
||||
overrideName?: boolean;
|
||||
icon?: string;
|
||||
env?: ITerminalEnvironment;
|
||||
}
|
||||
|
||||
export interface ITerminalExecutable extends IBaseUnresolvedTerminalProfile {
|
||||
path: string | string[];
|
||||
}
|
||||
|
||||
export interface ITerminalProfileSource extends IBaseUnresolvedTerminalProfile {
|
||||
source: ProfileSource;
|
||||
}
|
||||
|
||||
export type ITerminalProfileObject = ITerminalExecutable | ITerminalProfileSource | null;
|
||||
|
||||
export interface IAvailableProfilesRequest {
|
||||
callback: (shells: ITerminalProfile[]) => void;
|
||||
configuredProfilesOnly: boolean;
|
||||
}
|
||||
export interface IDefaultShellAndArgsRequest {
|
||||
useAutomationShell: boolean;
|
||||
callback: (shell: string, args: string[] | string | undefined) => void;
|
||||
|
@ -412,78 +374,6 @@ export enum TitleEventSource {
|
|||
|
||||
export const QUICK_LAUNCH_PROFILE_CHOICE = 'workbench.action.terminal.profile.choice';
|
||||
|
||||
export const enum TerminalSettingId {
|
||||
ShellLinux = 'terminal.integrated.shell.linux',
|
||||
ShellMacOs = 'terminal.integrated.shell.osx',
|
||||
ShellWindows = 'terminal.integrated.shell.windows',
|
||||
SendKeybindingsToShell = 'terminal.integrated.sendKeybindingsToShell',
|
||||
AutomationShellLinux = 'terminal.integrated.automationShell.linux',
|
||||
AutomationShellMacOs = 'terminal.integrated.automationShell.osx',
|
||||
AutomationShellWindows = 'terminal.integrated.automationShell.windows',
|
||||
ShellArgsLinux = 'terminal.integrated.shellArgs.linux',
|
||||
ShellArgsMacOs = 'terminal.integrated.shellArgs.osx',
|
||||
ShellArgsWindows = 'terminal.integrated.shellArgs.windows',
|
||||
ProfilesWindows = 'terminal.integrated.profiles.windows',
|
||||
ProfilesMacOs = 'terminal.integrated.profiles.osx',
|
||||
ProfilesLinux = 'terminal.integrated.profiles.linux',
|
||||
DefaultProfileLinux = 'terminal.integrated.defaultProfile.linux',
|
||||
DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx',
|
||||
DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows',
|
||||
UseWslProfiles = 'terminal.integrated.useWslProfiles',
|
||||
TabsEnabled = 'terminal.integrated.tabs.enabled',
|
||||
TabsHideCondition = 'terminal.integrated.tabs.hideCondition',
|
||||
TabsShowActiveTerminal = 'terminal.integrated.tabs.showActiveTerminal',
|
||||
TabsLocation = 'terminal.integrated.tabs.location',
|
||||
TabsFocusMode = 'terminal.integrated.tabs.focusMode',
|
||||
MacOptionIsMeta = 'terminal.integrated.macOptionIsMeta',
|
||||
MacOptionClickForcesSelection = 'terminal.integrated.macOptionClickForcesSelection',
|
||||
AltClickMovesCursor = 'terminal.integrated.altClickMovesCursor',
|
||||
CopyOnSelection = 'terminal.integrated.copyOnSelection',
|
||||
DrawBoldTextInBrightColors = 'terminal.integrated.drawBoldTextInBrightColors',
|
||||
FontFamily = 'terminal.integrated.fontFamily',
|
||||
FontSize = 'terminal.integrated.fontSize',
|
||||
LetterSpacing = 'terminal.integrated.letterSpacing',
|
||||
LineHeight = 'terminal.integrated.lineHeight',
|
||||
MinimumContrastRatio = 'terminal.integrated.minimumContrastRatio',
|
||||
FastScrollSensitivity = 'terminal.integrated.fastScrollSensitivity',
|
||||
MouseWheelScrollSensitivity = 'terminal.integrated.mouseWheelScrollSensitivity',
|
||||
BellDuration = 'terminal.integrated.bellDuration',
|
||||
FontWeight = 'terminal.integrated.fontWeight',
|
||||
FontWeightBold = 'terminal.integrated.fontWeightBold',
|
||||
CursorBlinking = 'terminal.integrated.cursorBlinking',
|
||||
CursorStyle = 'terminal.integrated.cursorStyle',
|
||||
CursorWidth = 'terminal.integrated.cursorWidth',
|
||||
Scrollback = 'terminal.integrated.scrollback',
|
||||
DetectLocale = 'terminal.integrated.detectLocale',
|
||||
GpuAcceleration = 'terminal.integrated.gpuAcceleration',
|
||||
RightClickBehavior = 'terminal.integrated.rightClickBehavior',
|
||||
Cwd = 'terminal.integrated.cwd',
|
||||
ConfirmOnExit = 'terminal.integrated.confirmOnExit',
|
||||
EnableBell = 'terminal.integrated.enableBell',
|
||||
CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell',
|
||||
AllowChords = 'terminal.integrated.allowChords',
|
||||
AllowMnemonics = 'terminal.integrated.allowMnemonics',
|
||||
EnvMacOs = 'terminal.integrated.env.osx',
|
||||
EnvLinux = 'terminal.integrated.env.linux',
|
||||
EnvWindows = 'terminal.integrated.env.windows',
|
||||
EnvironmentChangesIndicator = 'terminal.integrated.environmentChangesIndicator',
|
||||
EnvironmentChangesRelaunch = 'terminal.integrated.environmentChangesRelaunch',
|
||||
ShowExitAlert = 'terminal.integrated.showExitAlert',
|
||||
SplitCwd = 'terminal.integrated.splitCwd',
|
||||
WindowsEnableConpty = 'terminal.integrated.windowsEnableConpty',
|
||||
WordSeparators = 'terminal.integrated.wordSeparators',
|
||||
ExperimentalUseTitleEvent = 'terminal.integrated.experimentalUseTitleEvent',
|
||||
EnableFileLinks = 'terminal.integrated.enableFileLinks',
|
||||
UnicodeVersion = 'terminal.integrated.unicodeVersion',
|
||||
ExperimentalLinkProvider = 'terminal.integrated.experimentalLinkProvider',
|
||||
LocalEchoLatencyThreshold = 'terminal.integrated.localEchoLatencyThreshold',
|
||||
LocalEchoExcludePrograms = 'terminal.integrated.localEchoExcludePrograms',
|
||||
LocalEchoStyle = 'terminal.integrated.localEchoStyle',
|
||||
EnablePersistentSessions = 'terminal.integrated.enablePersistentSessions',
|
||||
AllowWorkspaceConfiguration = 'terminal.integrated.allowWorkspaceConfiguration',
|
||||
InheritEnv = 'terminal.integrated.inheritEnv'
|
||||
}
|
||||
|
||||
export const enum TerminalCommandId {
|
||||
FindNext = 'workbench.action.terminal.findNext',
|
||||
FindPrevious = 'workbench.action.terminal.findPrevious',
|
||||
|
|
|
@ -3,55 +3,14 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConfigurationScope, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE, TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
const terminalProfileSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
required: ['path'],
|
||||
properties: {
|
||||
path: {
|
||||
description: localize('terminalProfile.path', 'A single path to a shell executable or an array of paths that will be used as fallbacks when one fails.'),
|
||||
type: ['string', 'array'],
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
args: {
|
||||
description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
overrideName: {
|
||||
description: localize('terminalProfile.overrideName', 'Controls whether or not the profile name overrides the auto detected one.'),
|
||||
type: 'boolean'
|
||||
},
|
||||
icon: {
|
||||
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
|
||||
type: 'string'
|
||||
},
|
||||
env: {
|
||||
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: ['string', 'null']
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const shellDeprecationMessageLinux = localize('terminal.integrated.shell.linux.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.linux#`', '`#terminal.integrated.defaultProfile.linux#`');
|
||||
const shellDeprecationMessageOsx = localize('terminal.integrated.shell.osx.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.osx#`', '`#terminal.integrated.defaultProfile.osx#`');
|
||||
const shellDeprecationMessageWindows = localize('terminal.integrated.shell.windows.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.windows#`', '`#terminal.integrated.defaultProfile.windows#`');
|
||||
|
||||
export const terminalConfiguration: IConfigurationNode = {
|
||||
const terminalConfiguration: IConfigurationNode = {
|
||||
id: 'terminal',
|
||||
order: 100,
|
||||
title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
|
||||
|
@ -62,252 +21,6 @@ export const terminalConfiguration: IConfigurationNode = {
|
|||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
[TerminalSettingId.AutomationShellLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.linux',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.AutomationShellMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.osx',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.AutomationShellWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.windows',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.ShellLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageLinux
|
||||
},
|
||||
[TerminalSettingId.ShellMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageOsx
|
||||
},
|
||||
[TerminalSettingId.ShellWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageWindows
|
||||
},
|
||||
[TerminalSettingId.ShellArgsLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
default: [],
|
||||
markdownDeprecationMessage: shellDeprecationMessageLinux
|
||||
},
|
||||
[TerminalSettingId.ShellArgsMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
// Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This
|
||||
// is the reason terminals on macOS typically run login shells by default which set up
|
||||
// the environment. See http://unix.stackexchange.com/a/119675/115410
|
||||
default: ['-l'],
|
||||
markdownDeprecationMessage: shellDeprecationMessageOsx
|
||||
},
|
||||
[TerminalSettingId.ShellArgsWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
'anyOf': [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows.string', "The command line arguments in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6) to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
|
||||
}
|
||||
],
|
||||
default: [],
|
||||
markdownDeprecationMessage: shellDeprecationMessageWindows
|
||||
},
|
||||
[TerminalSettingId.ProfilesWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profiles.windows',
|
||||
comment: ['{0}, {1}, and {2} are the `source`, `path` and optional `args` settings keys']
|
||||
},
|
||||
"The Windows profiles to present when creating a new terminal via the terminal dropdown. Set to null to exclude them, use the {0} property to use the default detected configuration. Or, set the {1} and optional {2}", '`source`', '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'PowerShell': {
|
||||
source: 'PowerShell',
|
||||
icon: 'terminal-powershell'
|
||||
},
|
||||
'Command Prompt': {
|
||||
path: [
|
||||
'${env:windir}\\Sysnative\\cmd.exe',
|
||||
'${env:windir}\\System32\\cmd.exe'
|
||||
],
|
||||
args: [],
|
||||
icon: 'terminal-cmd'
|
||||
},
|
||||
'Git Bash': {
|
||||
source: 'Git Bash'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['source'],
|
||||
properties: {
|
||||
source: {
|
||||
description: localize('terminalProfile.windowsSource', 'A profile source that will auto detect the paths to the shell.'),
|
||||
enum: ['PowerShell', 'Git Bash']
|
||||
},
|
||||
overrideName: {
|
||||
description: localize('terminalProfile.overrideName', 'Controls whether or not the profile name overrides the auto detected one.'),
|
||||
type: 'boolean'
|
||||
},
|
||||
icon: {
|
||||
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
|
||||
type: 'string'
|
||||
},
|
||||
env: {
|
||||
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: ['string', 'null']
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.ProfilesMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profile.osx',
|
||||
comment: ['{0} and {1} are the `path` and optional `args` settings keys']
|
||||
},
|
||||
"The macOS profiles to present when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0} and optional {1}", '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash',
|
||||
icon: 'terminal-bash'
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh'
|
||||
},
|
||||
'fish': {
|
||||
path: 'fish'
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.ProfilesLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profile.linux',
|
||||
comment: ['{0} and {1} are the `path` and optional `args` settings keys']
|
||||
},
|
||||
"The Linux profiles to present when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0} and optional {1}", '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash'
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh'
|
||||
},
|
||||
'fish': {
|
||||
path: 'fish'
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.defaultProfile.linux', "The default profile used on Linux. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.linux#`', '`#terminal.integrated.shellArgs.linux#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileMacOs]: {
|
||||
restricted: true,
|
||||
description: localize('terminal.integrated.defaultProfile.osx', "The default profile used on macOS. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.osx#`', '`#terminal.integrated.shellArgs.osx#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileWindows]: {
|
||||
restricted: true,
|
||||
description: localize('terminal.integrated.defaultProfile.windows', "The default profile used on Windows. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.windows#`', '`#terminal.integrated.shellArgs.windows#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.UseWslProfiles]: {
|
||||
description: localize('terminal.integrated.useWslProfiles', 'Controls whether or not WSL distros are shown in the terminal dropdown'),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
[TerminalSettingId.TabsEnabled]: {
|
||||
description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'),
|
||||
type: 'boolean',
|
||||
|
@ -393,7 +106,7 @@ export const terminalConfiguration: IConfigurationNode = {
|
|||
[TerminalSettingId.FontSize]: {
|
||||
description: localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal."),
|
||||
type: 'number',
|
||||
default: EDITOR_FONT_DEFAULTS.fontSize
|
||||
default: isMacintosh ? 12 : 14
|
||||
},
|
||||
[TerminalSettingId.LetterSpacing]: {
|
||||
description: localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."),
|
||||
|
@ -681,17 +394,10 @@ export const terminalConfiguration: IConfigurationNode = {
|
|||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
[TerminalSettingId.AllowWorkspaceConfiguration]: {
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
description: localize('terminal.integrated.allowWorkspaceConfiguration', "Allows shell and profile settings to be pick up from a workspace."),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
[TerminalSettingId.InheritEnv]: {
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export function registerTerminalConfiguration() {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration(terminalConfiguration);
|
||||
}
|
||||
|
|
|
@ -9,9 +9,8 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
|||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, ITerminalEnvironment, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessEnvironment, isWindows, locale, OperatingSystem, OS, platform, Platform } from 'vs/base/common/platform';
|
||||
import { TerminalSettingId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
/**
|
||||
* This module contains utility functions related to the environment, cwd and paths.
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
|
@ -16,7 +18,9 @@ import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs
|
|||
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { LocalPty } from 'vs/workbench/contrib/terminal/electron-sandbox/localPty';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
|
||||
export class LocalTerminalService extends Disposable implements ILocalTerminalService {
|
||||
declare _serviceBrand: undefined;
|
||||
|
@ -38,7 +42,9 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe
|
|||
@ILocalPtyService private readonly _localPtyService: ILocalPtyService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService
|
||||
@IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService,
|
||||
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
|
||||
@IHistoryService historyService: IHistoryService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -96,6 +102,17 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe
|
|||
this._onPtyHostResponsive.fire();
|
||||
}));
|
||||
}
|
||||
if (this._localPtyService.onPtyHostRequestResolveVariables) {
|
||||
this._register(this._localPtyService.onPtyHostRequestResolveVariables(async e => {
|
||||
const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file);
|
||||
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
|
||||
const resolveCalls: Promise<string>[] = e.originalText.map(t => {
|
||||
return configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, t);
|
||||
});
|
||||
const result = await Promise.all(resolveCalls);
|
||||
this._localPtyService.acceptPtyHostResolvedVariables?.(e.id, result);
|
||||
}));
|
||||
}
|
||||
}
|
||||
async updateTitle(id: number, title: string): Promise<void> {
|
||||
await this._localPtyService.updateTitle(id, title);
|
||||
|
@ -137,6 +154,10 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe
|
|||
return this._localPtyService.getDefaultSystemShell(osOverride);
|
||||
}
|
||||
|
||||
async getProfiles(includeDetectedProfiles?: boolean) {
|
||||
return this._localPtyService.getProfiles?.(includeDetectedProfiles) || [];
|
||||
}
|
||||
|
||||
async getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return this._localPtyService.getEnvironment();
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
|
||||
import { deepStrictEqual, fail, ok, strictEqual } from 'assert';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { SafeConfigProvider } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalConfiguration, ITerminalProfile, ITerminalProfiles, ProfileSource } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { detectAvailableProfiles, IFsProvider } from 'vs/workbench/contrib/terminal/node/terminalProfiles';
|
||||
import { ITerminalProfile, ProfileSource, SafeConfigProvider } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalConfiguration, ITerminalProfiles } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { detectAvailableProfiles, IFsProvider } from 'vs/platform/terminal/node/terminalProfiles';
|
||||
|
||||
/**
|
||||
* Assets that two profiles objects are equal, this will treat explicit undefined and unset
|
||||
* properties the same. Order of the profiles is ignored.
|
||||
*/
|
||||
function profilesEqual(actualProfiles: ITerminalProfile[], expectedProfiles: ITerminalProfile[]) {
|
||||
strictEqual(actualProfiles.length, expectedProfiles.length);
|
||||
strictEqual(actualProfiles.length, expectedProfiles.length, `Actual: ${actualProfiles.map(e => e.profileName).join(',')}\nExpected: ${expectedProfiles.map(e => e.profileName).join(',')}`);
|
||||
for (const expected of expectedProfiles) {
|
||||
const actual = actualProfiles.find(e => e.profileName === expected.profileName);
|
||||
ok(actual, `Expected profile ${expected.profileName} not found`);
|
||||
|
@ -71,7 +71,7 @@ suite('Workbench - TerminalProfiles', () => {
|
|||
const config: ITestTerminalConfig = {
|
||||
profiles: {
|
||||
windows: {
|
||||
'PowerShell NoProfile': { source: ProfileSource.Pwsh, args: ['-NoProfile'], overrideName: true }
|
||||
'PowerShell': { source: ProfileSource.Pwsh, args: ['-NoProfile'], overrideName: true }
|
||||
},
|
||||
linux: {},
|
||||
osx: {},
|
||||
|
@ -80,7 +80,7 @@ suite('Workbench - TerminalProfiles', () => {
|
|||
};
|
||||
const profiles = await detectAvailableProfiles(true, buildTestSafeConfigProvider(config), fsProvider, undefined, undefined, undefined);
|
||||
const expected = [
|
||||
{ profileName: 'PowerShell NoProfile', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', overrideName: true, args: ['-NoProfile'], isDefault: true }
|
||||
{ profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', overrideName: true, args: ['-NoProfile'], isDefault: true }
|
||||
];
|
||||
profilesEqual(profiles, expected);
|
||||
});
|
||||
|
|
|
@ -125,11 +125,11 @@ import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEd
|
|||
import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
|
||||
import { ILocalTerminalService, IShellLaunchConfig, ITerminalChildProcess, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { ILocalTerminalService, IShellLaunchConfig, ITerminalChildProcess, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessDetails, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { isArray } from 'vs/base/common/types';
|
||||
import { IShellLaunchConfigResolveOptions, ITerminalProfile, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { EditorOverrideService } from 'vs/workbench/services/editor/browser/editorOverrideService';
|
||||
import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService';
|
||||
|
@ -1597,6 +1597,7 @@ export class TestLocalTerminalService implements ILocalTerminalService {
|
|||
async attachToProcess(id: number): Promise<ITerminalChildProcess | undefined> { throw new Error('Method not implemented.'); }
|
||||
async listProcesses(): Promise<IProcessDetails[]> { throw new Error('Method not implemented.'); }
|
||||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> { throw new Error('Method not implemented.'); }
|
||||
getProfiles(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]> { throw new Error('Method not implemented.'); }
|
||||
getEnvironment(): Promise<IProcessEnvironment> { throw new Error('Method not implemented.'); }
|
||||
getShellEnvironment(): Promise<IProcessEnvironment | undefined> { throw new Error('Method not implemented.'); }
|
||||
getWslPath(original: string): Promise<string> { throw new Error('Method not implemented.'); }
|
||||
|
|
Loading…
Reference in a new issue