Merge from vscode 8b5ebbb1b8f6b2127bbbd551ac10cc080482d5b4 (#5041)

This commit is contained in:
Anthony Dresser
2019-04-15 20:37:22 -07:00
committed by GitHub
parent dcdbc95ae7
commit a5bc65fbfb
48 changed files with 495 additions and 294 deletions

View File

@@ -10,6 +10,7 @@
"vscode": "^1.20.0" "vscode": "^1.20.0"
}, },
"main": "./out/extension", "main": "./out/extension",
"extensionKind": "ui",
"categories": [ "categories": [
"Programming Languages" "Programming Languages"
], ],

View File

@@ -23,8 +23,8 @@
"selection.background": "#ccccc7", "selection.background": "#ccccc7",
"editor.selectionHighlightBackground": "#575b6180", "editor.selectionHighlightBackground": "#575b6180",
"editor.selectionBackground": "#878b9180", "editor.selectionBackground": "#878b9180",
"editor.wordHighlightBackground": "#4a4a7680", "editor.wordHighlightBackground": "#4a4a7680",
"editor.wordHighlightStrongBackground": "#6a6a9680", "editor.wordHighlightStrongBackground": "#6a6a9680",
"editor.lineHighlightBackground": "#3e3d32", "editor.lineHighlightBackground": "#3e3d32",
"editorLineNumber.activeForeground": "#c2c2bf", "editorLineNumber.activeForeground": "#c2c2bf",
"editorCursor.foreground": "#f8f8f0", "editorCursor.foreground": "#f8f8f0",

View File

@@ -163,7 +163,7 @@ function darkenColor(color) {
for (let i = 1; i < 7; i += 2) { for (let i = 1; i < 7; i += 2) {
let newVal = Math.round(parseInt('0x' + color.substr(i, 2), 16) * 0.9); let newVal = Math.round(parseInt('0x' + color.substr(i, 2), 16) * 0.9);
let hex = newVal.toString(16); let hex = newVal.toString(16);
if (hex.length == 1) { if (hex.length === 1) {
res += '0'; res += '0';
} }
res += hex; res += hex;

View File

@@ -5,6 +5,7 @@
COMMIT="@@COMMIT@@" COMMIT="@@COMMIT@@"
APP_NAME="@@APPNAME@@" APP_NAME="@@APPNAME@@"
QUALITY="@@QUALITY@@" QUALITY="@@QUALITY@@"
NAME="@@NAME@@"
set -e set -e
@@ -22,6 +23,8 @@ if grep -qi Microsoft /proc/version; then
fi fi
fi fi
VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")"
if [ -x "$(command -v cygpath)" ]; then if [ -x "$(command -v cygpath)" ]; then
CLI=$(cygpath -m "$VSCODE_PATH/resources/app/out/cli.js") CLI=$(cygpath -m "$VSCODE_PATH/resources/app/out/cli.js")
else else

View File

@@ -59,19 +59,19 @@ function code-wsl()
{ {
# in a wsl shell # in a wsl shell
local WIN_CODE_CLI_CMD=$(wslpath -w "$ROOT/scripts/code-cli.bat") local WIN_CODE_CLI_CMD=$(wslpath -w "$ROOT/scripts/code-cli.bat")
if ! [ -z "$WIN_CODE_CLI_CMD" ]; then
local WSL_EXT_ID="ms-vscode.remote-wsl" local WSL_EXT_ID="ms-vscode.remote-wsl"
local WSL_EXT_WLOC=$(cmd.exe /c "$WIN_CODE_CLI_CMD" --locate-extension $WSL_EXT_ID) local WSL_EXT_WLOC=$(cmd.exe /c "$WIN_CODE_CLI_CMD" --locate-extension $WSL_EXT_ID)
if ! [ -z "$WSL_EXT_WLOC" ]; then if ! [ -z "$WSL_EXT_WLOC" ]; then
# replace \r\n with \n in WSL_EXT_WLOC # replace \r\n with \n in WSL_EXT_WLOC
local WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode-dev.sh local WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode-dev.sh
$WSL_CODE "$ROOT" "$@" $WSL_CODE "$ROOT" "$@"
exit $? exit $?
fi
fi fi
} }
if [ -z ${IN_WSL+x} ]; then if ! [ -z ${IN_WSL+x} ]; then
code "$@"
else
code-wsl "$@" code-wsl "$@"
fi fi
code "$@"

View File

@@ -20,6 +20,7 @@ export interface IDialogOptions {
cancelId?: number; cancelId?: number;
detail?: string; detail?: string;
type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending'; type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending';
keyEventProcessor?: (event: StandardKeyboardEvent) => void;
} }
export interface IDialogStyles extends IButtonStyles { export interface IDialogStyles extends IButtonStyles {
@@ -103,19 +104,26 @@ export class Dialog extends Disposable {
return; return;
} }
let eventHandled = false;
if (this.buttonGroup) { if (this.buttonGroup) {
if (evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) { if (evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) {
focusedButton = focusedButton + this.buttonGroup.buttons.length - 1; focusedButton = focusedButton + this.buttonGroup.buttons.length - 1;
focusedButton = focusedButton % this.buttonGroup.buttons.length; focusedButton = focusedButton % this.buttonGroup.buttons.length;
this.buttonGroup.buttons[focusedButton].focus(); this.buttonGroup.buttons[focusedButton].focus();
eventHandled = true;
} else if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) { } else if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) {
focusedButton++; focusedButton++;
focusedButton = focusedButton % this.buttonGroup.buttons.length; focusedButton = focusedButton % this.buttonGroup.buttons.length;
this.buttonGroup.buttons[focusedButton].focus(); this.buttonGroup.buttons[focusedButton].focus();
eventHandled = true;
} }
} }
EventHelper.stop(e, true); if (eventHandled) {
EventHelper.stop(e, true);
} else if (this.options.keyEventProcessor) {
this.options.keyEventProcessor(evt);
}
})); }));
this._register(domEvent(window, 'keyup', true)((e: KeyboardEvent) => { this._register(domEvent(window, 'keyup', true)((e: KeyboardEvent) => {

View File

@@ -5,7 +5,7 @@
.monaco-sash { .monaco-sash {
position: absolute; position: absolute;
z-index: 90; z-index: 35;
touch-action: none; touch-action: none;
} }

View File

@@ -63,7 +63,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/> <input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
<label class="caption" for="includeSystemInfo">${escape(localize({ <label class="caption" for="includeSystemInfo">${escape(localize({
key: 'sendSystemInfo', key: 'sendSystemInfo',
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the system information'] comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the system information']
}, "Include my system information ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label> }, "Include my system information ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
<div class="block-info hidden"> <div class="block-info hidden">
<!-- To be dynamically filled --> <!-- To be dynamically filled -->
@@ -73,7 +73,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/> <input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
<label class="caption" for="includeProcessInfo">${escape(localize({ <label class="caption" for="includeProcessInfo">${escape(localize({
key: 'sendProcessInfo', key: 'sendProcessInfo',
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the process info'] comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the process info']
}, "Include my currently running processes ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label> }, "Include my currently running processes ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
<pre class="block-info hidden"> <pre class="block-info hidden">
<code> <code>
@@ -85,7 +85,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/> <input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
<label class="caption" for="includeWorkspaceInfo">${escape(localize({ <label class="caption" for="includeWorkspaceInfo">${escape(localize({
key: 'sendWorkspaceInfo', key: 'sendWorkspaceInfo',
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the workspace information'] comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the workspace information']
}, "Include my workspace metadata ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label> }, "Include my workspace metadata ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
<pre id="systemInfo" class="block-info hidden"> <pre id="systemInfo" class="block-info hidden">
<code> <code>
@@ -97,7 +97,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeExtensions" checked/> <input class="sendData" type="checkbox" id="includeExtensions" checked/>
<label class="caption" for="includeExtensions">${escape(localize({ <label class="caption" for="includeExtensions">${escape(localize({
key: 'sendExtensions', key: 'sendExtensions',
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the enabled extensions list'] comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the enabled extensions list']
}, "Include my enabled extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label> }, "Include my enabled extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
<div id="systemInfo" class="block-info hidden"> <div id="systemInfo" class="block-info hidden">
<!-- To be dynamically filled --> <!-- To be dynamically filled -->
@@ -107,7 +107,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/> <input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
<label class="caption" for="includeSearchedExtensions">${escape(localize({ <label class="caption" for="includeSearchedExtensions">${escape(localize({
key: 'sendSearchedExtensions', key: 'sendSearchedExtensions',
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the searched extensions'] comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the searched extensions']
}, "Send searched extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label> }, "Send searched extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
<div class="block-info hidden"> <div class="block-info hidden">
<!-- To be dynamically filled --> <!-- To be dynamically filled -->
@@ -117,7 +117,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/> <input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
<label class="caption" for="includeSettingsSearchDetails">${escape(localize({ <label class="caption" for="includeSettingsSearchDetails">${escape(localize({
key: 'sendSettingsSearchDetails', key: 'sendSettingsSearchDetails',
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the search details'] comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the search details']
}, "Send settings search details ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label> }, "Send settings search details ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
<div class="block-info hidden"> <div class="block-info hidden">
<!-- To be dynamically filled --> <!-- To be dynamically filled -->

View File

@@ -543,7 +543,7 @@ export class TextAreaHandler extends ViewPart {
} }
// (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea) // (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea)
// specifically, when doing Korean IME, setting the textare to 0x0 breaks IME badly. // specifically, when doing Korean IME, setting the textarea to 0x0 breaks IME badly.
ta.setWidth(1); ta.setWidth(1);
ta.setHeight(1); ta.setHeight(1);

View File

@@ -344,7 +344,7 @@ export class TextAreaInput extends Disposable {
// //
// The problems with the `selectionchange` event are: // The problems with the `selectionchange` event are:
// * the event is emitted when the textarea is focused programmatically -- textarea.focus() // * the event is emitted when the textarea is focused programmatically -- textarea.focus()
// * the event is emitted when the selection is changed in the textarea programatically -- textarea.setSelectionRange(...) // * the event is emitted when the selection is changed in the textarea programmatically -- textarea.setSelectionRange(...)
// * the event is emitted when the value of the textarea is changed programmatically -- textarea.value = '...' // * the event is emitted when the value of the textarea is changed programmatically -- textarea.value = '...'
// * the event is emitted when tabbing into the textarea // * the event is emitted when tabbing into the textarea
// * the event is emitted asynchronously (sometimes with a delay as high as a few tens of ms) // * the event is emitted asynchronously (sometimes with a delay as high as a few tens of ms)

View File

@@ -1408,6 +1408,14 @@ export interface WorkspaceCommentProvider {
onDidChangeCommentThreads(): Event<CommentThreadChangedEvent>; onDidChangeCommentThreads(): Event<CommentThreadChangedEvent>;
} }
/**
* @internal
*/
export interface IWebviewPortMapping {
webviewPort: number;
extensionHostPort: number;
}
/** /**
* @internal * @internal
*/ */
@@ -1415,7 +1423,7 @@ export interface IWebviewOptions {
readonly enableScripts?: boolean; readonly enableScripts?: boolean;
readonly enableCommandUris?: boolean; readonly enableCommandUris?: boolean;
readonly localResourceRoots?: ReadonlyArray<URI>; readonly localResourceRoots?: ReadonlyArray<URI>;
readonly portMapping?: ReadonlyArray<{ port: number, resolvedPort: number }>; readonly portMapping?: ReadonlyArray<IWebviewPortMapping>;
} }
/** /**

View File

@@ -13,6 +13,8 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachDialogStyler } from 'vs/platform/theme/common/styler'; import { attachDialogStyler } from 'vs/platform/theme/common/styler';
import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EventHelper } from 'vs/base/browser/dom';
export class DialogService implements IDialogService { export class DialogService implements IDialogService {
_serviceBrand: any; _serviceBrand: any;
@@ -76,7 +78,10 @@ export class DialogService implements IDialogService {
{ {
detail: options ? options.detail : undefined, detail: options ? options.detail : undefined,
cancelId: options ? options.cancelId : undefined, cancelId: options ? options.cancelId : undefined,
type: this.getDialogType(severity) type: this.getDialogType(severity),
keyEventProcessor: (event: StandardKeyboardEvent) => {
EventHelper.stop(event, true);
}
}); });
dialogDisposables.push(dialog); dialogDisposables.push(dialog);

View File

@@ -206,12 +206,14 @@ export function buildHelpMessage(productName: string, executableName: string, ve
help.push(''); help.push('');
help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`); help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`);
help.push(''); help.push('');
if (os.platform() === 'win32') { if (isPipeSupported) {
help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", executableName)); if (os.platform() === 'win32') {
} else { help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", executableName));
help.push(localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", executableName)); } else {
help.push(localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", executableName));
}
help.push('');
} }
help.push('');
for (let key in categories) { for (let key in categories) {
let categoryOptions = options.filter(o => !!o.description && o.cat === key && isOptionSupported(o)); let categoryOptions = options.filter(o => !!o.description && o.cat === key && isOptionSupported(o));
if (categoryOptions.length) { if (categoryOptions.length) {

View File

@@ -12,13 +12,11 @@ import product from 'vs/platform/product/node/product';
export function isUIExtension(manifest: IExtensionManifest, uiContributions: string[], configurationService: IConfigurationService): boolean { export function isUIExtension(manifest: IExtensionManifest, uiContributions: string[], configurationService: IConfigurationService): boolean {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const configuredUIExtensions = configurationService.getValue<string[]>('_workbench.uiExtensions') || []; const configuredUIExtensions = configurationService.getValue<string[]>('_workbench.uiExtensions') || [];
if (configuredUIExtensions.length) { if (configuredUIExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
if (configuredUIExtensions.indexOf(extensionId) !== -1) { return true;
return true; }
} if (configuredUIExtensions.some(id => areSameExtensions({ id }, { id: `-${extensionId}` }))) {
if (configuredUIExtensions.indexOf(`-${extensionId}`) !== -1) { return false;
return false;
}
} }
switch (manifest.extensionKind) { switch (manifest.extensionKind) {
case 'ui': return true; case 'ui': return true;

View File

@@ -162,6 +162,16 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
readonly onDidAccept: Event<void>; readonly onDidAccept: Event<void>;
ok: boolean;
readonly onDidCustom: Event<void>;
customButton: boolean;
customLabel: string;
customHover: string;
buttons: ReadonlyArray<IQuickInputButton>; buttons: ReadonlyArray<IQuickInputButton>;
readonly onDidTriggerButton: Event<IQuickInputButton>; readonly onDidTriggerButton: Event<IQuickInputButton>;

View File

@@ -7,6 +7,7 @@ import { Client, PersistentProtocol, ISocket } from 'vs/base/parts/ipc/common/ip
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
export const enum ConnectionType { export const enum ConnectionType {
Management = 1, Management = 1,
@@ -147,8 +148,38 @@ export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunn
return protocol; return protocol;
} }
export const enum PersistenConnectionEventType {
ConnectionLost,
ReconnectionWait,
ReconnectionRunning,
ReconnectionPermanentFailure,
ConnectionGain
}
export class ConnectionLostEvent {
public readonly type = PersistenConnectionEventType.ConnectionLost;
}
export class ReconnectionWaitEvent {
public readonly type = PersistenConnectionEventType.ReconnectionWait;
constructor(
public readonly durationSeconds: number
) { }
}
export class ReconnectionRunningEvent {
public readonly type = PersistenConnectionEventType.ReconnectionRunning;
}
export class ConnectionGainEvent {
public readonly type = PersistenConnectionEventType.ConnectionGain;
}
export class ReconnectionPermanentFailureEvent {
public readonly type = PersistenConnectionEventType.ReconnectionPermanentFailure;
}
export type PersistenConnectionEvent = ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ConnectionGainEvent | ReconnectionPermanentFailureEvent;
abstract class PersistentConnection extends Disposable { abstract class PersistentConnection extends Disposable {
private readonly _onDidStateChange = this._register(new Emitter<PersistenConnectionEvent>());
public readonly onDidStateChange = this._onDidStateChange.event;
protected readonly _options: IConnectionOptions; protected readonly _options: IConnectionOptions;
public readonly reconnectionToken: string; public readonly reconnectionToken: string;
public readonly protocol: PersistentProtocol; public readonly protocol: PersistentProtocol;

27
src/vs/vscode.d.ts vendored
View File

@@ -5714,6 +5714,21 @@ declare module 'vscode' {
copy?(source: Uri, destination: Uri, options: { overwrite: boolean }): void | Thenable<void>; copy?(source: Uri, destination: Uri, options: { overwrite: boolean }): void | Thenable<void>;
} }
/**
* Defines a port mapping used for localhost inside the webview.
*/
export interface WebviewPortMapping {
/**
* Localhost port to remap inside the webview.
*/
readonly webviewPort: number;
/**
* Destination port. The `webviewPort` is resolved to this port.
*/
readonly extensionHostPort: number;
}
/** /**
* Content settings for a webview. * Content settings for a webview.
*/ */
@@ -5740,6 +5755,18 @@ declare module 'vscode' {
* Pass in an empty array to disallow access to any local resources. * Pass in an empty array to disallow access to any local resources.
*/ */
readonly localResourceRoots?: ReadonlyArray<Uri>; readonly localResourceRoots?: ReadonlyArray<Uri>;
/**
* Mappings of localhost ports used inside the webview.
*
* Port mapping allow webviews to transparently define how localhost ports are resolved. This can be used
* to allow using a static localhost port inside the webview that is resolved to random port that a service is
* running on.
*
* If a webview accesses localhost content, we recomend that you specify port mappings even if
* the `webviewPort` and `extensionHostPort` ports are the same.
*/
readonly portMapping?: ReadonlyArray<WebviewPortMapping>;
} }
/** /**

View File

@@ -1360,35 +1360,4 @@ declare module 'vscode' {
group?: string; group?: string;
} }
//#endregion //#endregion
//#region Webview Port mapping— mjbvz
/**
* Defines a port mapping used for localhost inside the webview.
*/
export interface WebviewPortMapping {
/**
* Localhost port to remap inside the webview.
*/
readonly port: number;
/**
* Destination port. The `port` is resolved to this port.
*/
readonly resolvedPort: number;
}
export interface WebviewOptions {
/**
* Mappings of localhost ports used inside the webview.
*
* Port mapping allow webviews to transparently define how localhost ports are resolved. This can be used
* to allow using a static localhost port inside the webview that is resolved to random port that a service is
* running on.
*
* If a webview accesses localhost content, we recomend that you specify port mappings even if
* the `port` and `resolvedPort` ports are the same.
*/
readonly portMapping?: ReadonlyArray<WebviewPortMapping>;
}
//#endregion
} }

View File

@@ -51,6 +51,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
if (activeInstance) { if (activeInstance) {
this._proxy.$acceptActiveTerminalChanged(activeInstance.id); this._proxy.$acceptActiveTerminalChanged(activeInstance.id);
} }
this.terminalService.extHostReady(extHostContext.remoteAuthority);
} }
public dispose(): void { public dispose(): void {

View File

@@ -8,7 +8,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape } from '../common/extHost.protocol'; import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -45,9 +45,9 @@ export class MainThreadWindow implements MainThreadWindowShape {
return this.windowService.isFocused(); return this.windowService.isFocused();
} }
async $openUri(uriComponent: UriComponents): Promise<boolean> { async $openUri(uriComponent: UriComponents, options: IOpenUriOptions): Promise<boolean> {
const uri = URI.revive(uriComponent); const uri = URI.revive(uriComponent);
if (!!this.environmentService.configuration.remoteAuthority) { if (options.allowTunneling && !!this.environmentService.configuration.remoteAuthority) {
if (uri.scheme === 'http' || uri.scheme === 'https') { if (uri.scheme === 'http' || uri.scheme === 'https') {
const port = this.getLocalhostPort(uri); const port = this.getLocalhostPort(uri);
if (typeof port === 'number') { if (typeof port === 'number') {

View File

@@ -539,7 +539,7 @@ export interface ExtHostWebviewsShape {
$onMessage(handle: WebviewPanelHandle, message: any): void; $onMessage(handle: WebviewPanelHandle, message: any): void;
$onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, newState: WebviewPanelViewState): void; $onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, newState: WebviewPanelViewState): void;
$onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void>; $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void>;
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions): Promise<void>; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
} }
export interface MainThreadUrlsShape extends IDisposable { export interface MainThreadUrlsShape extends IDisposable {
@@ -689,9 +689,13 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise<void>; $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise<void>;
} }
export interface IOpenUriOptions {
readonly allowTunneling?: boolean;
}
export interface MainThreadWindowShape extends IDisposable { export interface MainThreadWindowShape extends IDisposable {
$getWindowVisibility(): Promise<boolean>; $getWindowVisibility(): Promise<boolean>;
$openUri(uri: UriComponents): Promise<boolean>; $openUri(uri: UriComponents, options: IOpenUriOptions): Promise<boolean>;
} }
// -- extension host // -- extension host

View File

@@ -11,6 +11,7 @@ import * as vscode from 'vscode';
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState, WebviewInsetHandle } from './extHost.protocol'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState, WebviewInsetHandle } from './extHost.protocol';
import { Disposable } from './extHostTypes'; import { Disposable } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as modes from 'vs/editor/common/modes';
type IconPath = URI | { light: URI, dark: URI }; type IconPath = URI | { light: URI, dark: URI };
@@ -58,7 +59,7 @@ export class ExtHostWebview implements vscode.Webview {
public set options(newOptions: vscode.WebviewOptions) { public set options(newOptions: vscode.WebviewOptions) {
this.assertNotDisposed(); this.assertNotDisposed();
this._proxy.$setOptions(this._handle, newOptions); this._proxy.$setOptions(this._handle, convertWebviewOptions(newOptions));
this._options = newOptions; this._options = newOptions;
} }
@@ -257,7 +258,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
}; };
const handle = ExtHostWebviews.newHandle(); const handle = ExtHostWebviews.newHandle();
this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, options, extension.identifier, extension.extensionLocation); this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, convertWebviewOptions(options), extension.identifier, extension.extensionLocation);
const webview = new ExtHostWebview(handle, this._proxy, options); const webview = new ExtHostWebview(handle, this._proxy, options);
const panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview); const panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview);
@@ -325,7 +326,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
title: string, title: string,
state: any, state: any,
position: EditorViewColumn, position: EditorViewColumn,
options: vscode.WebviewOptions & vscode.WebviewPanelOptions options: modes.IWebviewOptions & modes.IWebviewPanelOptions
): Promise<void> { ): Promise<void> {
const serializer = this._serializers.get(viewType); const serializer = this._serializers.get(viewType);
if (!serializer) { if (!serializer) {
@@ -342,3 +343,20 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
return this._webviewPanels.get(handle); return this._webviewPanels.get(handle);
} }
} }
function convertWebviewOptions(
options: vscode.WebviewPanelOptions & vscode.WebviewOptions
): modes.IWebviewOptions {
return {
...options,
portMapping: options.portMapping
? options.portMapping.map((x): modes.IWebviewPortMapping => {
// Handle old proposed api
if ('port' in x) {
return { webviewPort: (x as any).port, extensionHostPort: (x as any).resolvedPort };
}
return { webviewPort: x.webviewPort, extensionHostPort: x.extensionHostPort };
})
: undefined,
};
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext } from './extHost.protocol'; import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext, IOpenUriOptions } from './extHost.protocol';
import { WindowState } from 'vscode'; import { WindowState } from 'vscode';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
@@ -38,7 +38,7 @@ export class ExtHostWindow implements ExtHostWindowShape {
this._onDidChangeWindowState.fire(this._state); this._onDidChangeWindowState.fire(this._state);
} }
openUri(stringOrUri: string | URI): Promise<boolean> { openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise<boolean> {
if (typeof stringOrUri === 'string') { if (typeof stringOrUri === 'string') {
try { try {
stringOrUri = URI.parse(stringOrUri); stringOrUri = URI.parse(stringOrUri);
@@ -51,6 +51,6 @@ export class ExtHostWindow implements ExtHostWindowShape {
} else if (stringOrUri.scheme === Schemas.command) { } else if (stringOrUri.scheme === Schemas.command) {
return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`); return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`);
} }
return this._proxy.$openUri(stringOrUri); return this._proxy.$openUri(stringOrUri, options);
} }
} }

View File

@@ -7,7 +7,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import * as map from 'vs/base/common/map'; import * as map from 'vs/base/common/map';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IWebviewOptions } from 'vs/editor/common/modes'; import * as modes from 'vs/editor/common/modes';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -122,7 +122,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
$createWebviewCodeInset( $createWebviewCodeInset(
handle: WebviewInsetHandle, handle: WebviewInsetHandle,
symbolId: string, symbolId: string,
options: IWebviewOptions, options: modes.IWebviewOptions,
extensionId: ExtensionIdentifier, extensionId: ExtensionIdentifier,
extensionLocation: UriComponents extensionLocation: UriComponents
): void { ): void {
@@ -189,7 +189,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
} }
} }
public $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: IWebviewOptions): void { public $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: modes.IWebviewOptions): void {
if (typeof handle === 'number') { if (typeof handle === 'number') {
this.getWebviewElement(handle).options = reviveWebviewOptions(options as any /*todo@mat */); this.getWebviewElement(handle).options = reviveWebviewOptions(options as any /*todo@mat */);
} else { } else {

View File

@@ -257,7 +257,7 @@ export function createApiFactory(
return extHostClipboard; return extHostClipboard;
}, },
openExternal(uri: URI) { openExternal(uri: URI) {
return extHostWindow.openUri(uri); return extHostWindow.openUri(uri, { allowTunneling: !!initData.remoteAuthority });
} }
}); });

View File

@@ -202,9 +202,9 @@ export class OpenNodeModuleFactory implements INodeModuleFactory {
return this.callOriginal(target, options); return this.callOriginal(target, options);
} }
if (uri.scheme === 'http' || uri.scheme === 'https') { if (uri.scheme === 'http' || uri.scheme === 'https') {
return mainThreadWindow.$openUri(uri); return mainThreadWindow.$openUri(uri, { allowTunneling: true });
} else if (uri.scheme === 'mailto') { } else if (uri.scheme === 'mailto') {
return mainThreadWindow.$openUri(uri); return mainThreadWindow.$openUri(uri, {});
} }
return this.callOriginal(target, options); return this.callOriginal(target, options);
}; };

View File

@@ -68,9 +68,11 @@ interface QuickInputUI {
visibleCount: CountBadge; visibleCount: CountBadge;
count: CountBadge; count: CountBadge;
message: HTMLElement; message: HTMLElement;
customButton: Button;
progressBar: ProgressBar; progressBar: ProgressBar;
list: QuickInputList; list: QuickInputList;
onDidAccept: Event<void>; onDidAccept: Event<void>;
onDidCustom: Event<void>;
onDidTriggerButton: Event<IQuickInputButton>; onDidTriggerButton: Event<IQuickInputButton>;
ignoreFocusOut: boolean; ignoreFocusOut: boolean;
keyMods: Writeable<IKeyMods>; keyMods: Writeable<IKeyMods>;
@@ -92,6 +94,7 @@ type Visibilities = {
message?: boolean; message?: boolean;
list?: boolean; list?: boolean;
ok?: boolean; ok?: boolean;
customButton?: boolean;
}; };
class QuickInput implements IQuickInput { class QuickInput implements IQuickInput {
@@ -312,6 +315,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _placeholder: string; private _placeholder: string;
private onDidChangeValueEmitter = new Emitter<string>(); private onDidChangeValueEmitter = new Emitter<string>();
private onDidAcceptEmitter = new Emitter<void>(); private onDidAcceptEmitter = new Emitter<void>();
private onDidCustomEmitter = new Emitter<void>();
private _items: Array<T | IQuickPickSeparator> = []; private _items: Array<T | IQuickPickSeparator> = [];
private itemsUpdated = false; private itemsUpdated = false;
private _canSelectMany = false; private _canSelectMany = false;
@@ -331,6 +335,10 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _valueSelection: Readonly<[number, number]>; private _valueSelection: Readonly<[number, number]>;
private valueSelectionUpdated = true; private valueSelectionUpdated = true;
private _validationMessage: string; private _validationMessage: string;
private _ok: boolean;
private _customButton: boolean;
private _customButtonLabel: string;
private _customButtonHover: string;
quickNavigate: IQuickNavigateConfiguration; quickNavigate: IQuickNavigateConfiguration;
@@ -339,6 +347,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.disposables.push( this.disposables.push(
this.onDidChangeValueEmitter, this.onDidChangeValueEmitter,
this.onDidAcceptEmitter, this.onDidAcceptEmitter,
this.onDidCustomEmitter,
this.onDidChangeActiveEmitter, this.onDidChangeActiveEmitter,
this.onDidChangeSelectionEmitter, this.onDidChangeSelectionEmitter,
this.onDidTriggerItemButtonEmitter, this.onDidTriggerItemButtonEmitter,
@@ -367,6 +376,8 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
onDidAccept = this.onDidAcceptEmitter.event; onDidAccept = this.onDidAcceptEmitter.event;
onDidCustom = this.onDidCustomEmitter.event;
get items() { get items() {
return this._items; return this._items;
} }
@@ -463,6 +474,42 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update(); this.update();
} }
get customButton() {
return this._customButton;
}
set customButton(showCustomButton: boolean) {
this._customButton = showCustomButton;
this.update();
}
get customLabel() {
return this._customButtonLabel;
}
set customLabel(label: string) {
this._customButtonLabel = label;
this.update();
}
get customHover() {
return this._customButtonHover;
}
set customHover(hover: string) {
this._customButtonHover = hover;
this.update();
}
get ok() {
return this._ok;
}
set ok(showOkButton: boolean) {
this._ok = showOkButton;
this.update();
}
public inputHasFocus(): boolean { public inputHasFocus(): boolean {
return this.visible ? this.ui.inputBox.hasFocus() : false; return this.visible ? this.ui.inputBox.hasFocus() : false;
} }
@@ -547,6 +594,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
} }
this.onDidAcceptEmitter.fire(undefined); this.onDidAcceptEmitter.fire(undefined);
}), }),
this.ui.onDidCustom(() => {
this.onDidCustomEmitter.fire(undefined);
}),
this.ui.list.onDidChangeFocus(focusedItems => { this.ui.list.onDidChangeFocus(focusedItems => {
if (this.activeItemsUpdated) { if (this.activeItemsUpdated) {
return; // Expect another event. return; // Expect another event.
@@ -697,12 +747,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.message.textContent = null; this.ui.message.textContent = null;
this.ui.inputBox.showDecoration(Severity.Ignore); this.ui.inputBox.showDecoration(Severity.Ignore);
} }
this.ui.customButton.label = this.customLabel;
this.ui.customButton.element.title = this.customHover;
this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel; this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.setComboboxAccessibility(true); this.ui.setComboboxAccessibility(true);
this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL);
this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage }); this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok });
} }
} }
@@ -848,6 +900,7 @@ export class QuickInputService extends Component implements IQuickInputService {
private countContainer: HTMLElement; private countContainer: HTMLElement;
private okContainer: HTMLElement; private okContainer: HTMLElement;
private ok: Button; private ok: Button;
private customButtonContainer: HTMLElement;
private ui: QuickInputUI; private ui: QuickInputUI;
private comboboxAccessibility = false; private comboboxAccessibility = false;
private enabled = true; private enabled = true;
@@ -855,6 +908,7 @@ export class QuickInputService extends Component implements IQuickInputService {
private inQuickOpenContext: IContextKey<boolean>; private inQuickOpenContext: IContextKey<boolean>;
private contexts: { [id: string]: IContextKey<boolean>; } = Object.create(null); private contexts: { [id: string]: IContextKey<boolean>; } = Object.create(null);
private onDidAcceptEmitter = this._register(new Emitter<void>()); private onDidAcceptEmitter = this._register(new Emitter<void>());
private onDidCustomEmitter = this._register(new Emitter<void>());
private onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>()); private onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false }; private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false };
@@ -1013,6 +1067,14 @@ export class QuickInputService extends Component implements IQuickInputService {
this.onDidAcceptEmitter.fire(); this.onDidAcceptEmitter.fire();
})); }));
this.customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
const customButton = new Button(this.customButtonContainer);
attachButtonStyler(customButton, this.themeService);
customButton.label = localize('custom', "Custom");
this._register(customButton.onDidClick(e => {
this.onDidCustomEmitter.fire();
}));
const message = dom.append(container, $(`#${this.idPrefix}message.quick-input-message`)); const message = dom.append(container, $(`#${this.idPrefix}message.quick-input-message`));
const progressBar = new ProgressBar(container); const progressBar = new ProgressBar(container);
@@ -1098,9 +1160,11 @@ export class QuickInputService extends Component implements IQuickInputService {
visibleCount, visibleCount,
count, count,
message, message,
customButton,
progressBar, progressBar,
list, list,
onDidAccept: this.onDidAcceptEmitter.event, onDidAccept: this.onDidAcceptEmitter.event,
onDidCustom: this.onDidCustomEmitter.event,
onDidTriggerButton: this.onDidTriggerButtonEmitter.event, onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
ignoreFocusOut: false, ignoreFocusOut: false,
keyMods: this.keyMods, keyMods: this.keyMods,
@@ -1330,6 +1394,7 @@ export class QuickInputService extends Component implements IQuickInputService {
this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none';
this.countContainer.style.display = visibilities.count ? '' : 'none'; this.countContainer.style.display = visibilities.count ? '' : 'none';
this.okContainer.style.display = visibilities.ok ? '' : 'none'; this.okContainer.style.display = visibilities.ok ? '' : 'none';
this.customButtonContainer.style.display = visibilities.customButton ? '' : 'none';
this.ui.message.style.display = visibilities.message ? '' : 'none'; this.ui.message.style.display = visibilities.message ? '' : 'none';
this.ui.list.display(!!visibilities.list); this.ui.list.display(!!visibilities.list);
this.ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); this.ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes');

View File

@@ -1060,10 +1060,10 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel {
const visibleViewDescriptors = this.viewsModel.visibleViewDescriptors; const visibleViewDescriptors = this.viewsModel.visibleViewDescriptors;
const toSetVisible = this.viewsModel.viewDescriptors const toSetVisible = this.viewsModel.viewDescriptors
.filter(d => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1); .filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1);
const toSetInvisible = visibleViewDescriptors const toSetInvisible = visibleViewDescriptors
.filter(d => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) === -1); .filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) === -1);
let size: number | undefined; let size: number | undefined;
const oneToOne = toSetVisible.length === 1 && toSetInvisible.length === 1; const oneToOne = toSetVisible.length === 1 && toSetInvisible.length === 1;
@@ -1077,10 +1077,12 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel {
} }
} }
viewDescriptor.repository.setSelected(false);
this.viewsModel.setVisible(viewDescriptor.id, false); this.viewsModel.setVisible(viewDescriptor.id, false);
} }
for (const viewDescriptor of toSetVisible) { for (const viewDescriptor of toSetVisible) {
viewDescriptor.repository.setSelected(true);
this.viewsModel.setVisible(viewDescriptor.id, true, size); this.viewsModel.setVisible(viewDescriptor.id, true, size);
} }
} }

View File

@@ -1972,7 +1972,7 @@ class TaskService extends Disposable implements ITaskService {
return entries; return entries;
} }
private showQuickPick(tasks: Promise<Task[]> | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): Promise<Task | undefined | null> { private showQuickPick(tasks: Promise<Task[]> | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise<TaskQuickPickEntry | undefined | null> {
let _createEntries = (): Promise<TaskQuickPickEntry[]> => { let _createEntries = (): Promise<TaskQuickPickEntry[]> => {
if (Array.isArray(tasks)) { if (Array.isArray(tasks)) {
return Promise.resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); return Promise.resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry));
@@ -1984,6 +1984,9 @@ class TaskService extends Disposable implements ITaskService {
if ((entries.length === 0) && defaultEntry) { if ((entries.length === 0) && defaultEntry) {
entries.push(defaultEntry); entries.push(defaultEntry);
} }
else if (entries.length > 1 && additionalEntries && additionalEntries.length > 0) {
entries.push(additionalEntries[0]);
}
return entries; return entries;
}), { }), {
placeHolder, placeHolder,
@@ -1997,7 +2000,7 @@ class TaskService extends Disposable implements ITaskService {
this.openConfig(task); this.openConfig(task);
} }
} }
}).then(entry => entry ? entry.task : undefined); });
} }
private showIgnoredFoldersMessage(): Promise<void> { private showIgnoredFoldersMessage(): Promise<void> {
@@ -2057,7 +2060,8 @@ class TaskService extends Disposable implements ITaskService {
task: null task: null
}, },
true). true).
then((task) => { then((entry) => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if (task === undefined) { if (task === undefined) {
return; return;
} }
@@ -2137,7 +2141,8 @@ class TaskService extends Disposable implements ITaskService {
label: nls.localize('TaskService.noBuildTask', 'No build task to run found. Configure Build Task...'), label: nls.localize('TaskService.noBuildTask', 'No build task to run found. Configure Build Task...'),
task: null task: null
}, },
true).then((task) => { true).then((entry) => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if (task === undefined) { if (task === undefined) {
return; return;
} }
@@ -2185,7 +2190,8 @@ class TaskService extends Disposable implements ITaskService {
label: nls.localize('TaskService.noTestTaskTerminal', 'No test task to run found. Configure Tasks...'), label: nls.localize('TaskService.noTestTaskTerminal', 'No test task to run found. Configure Tasks...'),
task: null task: null
}, true }, true
).then((task) => { ).then((entry) => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if (task === undefined) { if (task === undefined) {
return; return;
} }
@@ -2206,15 +2212,29 @@ class TaskService extends Disposable implements ITaskService {
if (!this.canRunCommand()) { if (!this.canRunCommand()) {
return; return;
} }
if (arg === 'terminateAll') {
this.terminateAll();
return;
}
let runQuickPick = (promise?: Promise<Task[]>) => { let runQuickPick = (promise?: Promise<Task[]>) => {
this.showQuickPick(promise || this.getActiveTasks(), this.showQuickPick(promise || this.getActiveTasks(),
nls.localize('TaskService.taskToTerminate', 'Select task to terminate'), nls.localize('TaskService.taskToTerminate', 'Select task to terminate'),
{ {
label: nls.localize('TaskService.noTaskRunning', 'No task is currently running'), label: nls.localize('TaskService.noTaskRunning', 'No task is currently running'),
task: null task: undefined
}, },
false, true false, true,
).then(task => { undefined,
[{
label: nls.localize('TaskService.terminateAllRunningTasks', 'All running tasks'),
id: 'terminateAll',
task: undefined
}]
).then(entry => {
if (entry && entry.id === 'terminateAll') {
this.terminateAll();
}
let task: Task | undefined | null = entry ? entry.task : undefined;
if (task === undefined || task === null) { if (task === undefined || task === null) {
return; return;
} }
@@ -2270,7 +2290,8 @@ class TaskService extends Disposable implements ITaskService {
task: null task: null
}, },
false, true false, true
).then(task => { ).then(entry => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if (task === undefined || task === null) { if (task === undefined || task === null) {
return; return;
} }
@@ -2482,7 +2503,8 @@ class TaskService extends Disposable implements ITaskService {
this.showIgnoredFoldersMessage().then(() => { this.showIgnoredFoldersMessage().then(() => {
this.showQuickPick(tasks, this.showQuickPick(tasks,
nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), undefined, true, false, selectedEntry). nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), undefined, true, false, selectedEntry).
then((task) => { then((entry) => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if ((task === undefined) || (task === null)) { if ((task === undefined) || (task === null)) {
return; return;
} }
@@ -2532,7 +2554,8 @@ class TaskService extends Disposable implements ITaskService {
this.showIgnoredFoldersMessage().then(() => { this.showIgnoredFoldersMessage().then(() => {
this.showQuickPick(tasks, this.showQuickPick(tasks,
nls.localize('TaskService.pickDefaultTestTask', 'Select the task to be used as the default test task'), undefined, true, false, selectedEntry).then((task) => { nls.localize('TaskService.pickDefaultTestTask', 'Select the task to be used as the default test task'), undefined, true, false, selectedEntry).then((entry) => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if (!task) { if (!task) {
return; return;
} }
@@ -2565,7 +2588,8 @@ class TaskService extends Disposable implements ITaskService {
task: null task: null
}, },
false, true false, true
).then((task) => { ).then((entry) => {
let task: Task | undefined | null = entry ? entry.task : undefined;
if (task === undefined || task === null) { if (task === undefined || task === null) {
return; return;
} }

View File

@@ -433,12 +433,10 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneRightT
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category); }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, {
primary: 0, primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow } mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category); }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, {
primary: 0, primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow } mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category); }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, {

View File

@@ -135,47 +135,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(hasRemoteAuthority ? REMOTE_HOST_SCHEME : undefined); const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(hasRemoteAuthority ? REMOTE_HOST_SCHEME : undefined);
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows); this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
} else { } else {
if (!shellLaunchConfig.executable) { this._process = this._launchProcess(shellLaunchConfig, cols, rows);
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd);
// Compel type system as process.env should not have any undefined entries
let env: platform.IProcessEnvironment = {};
if (shellLaunchConfig.strictEnv) {
// Only base the terminal process environment on this environment and add the
// various mixins when strictEnv is false
env = { ...shellLaunchConfig.env } as any;
} else {
// Merge process env with the env from config and from shellLaunchConfig
env = { ...process.env } as any;
// Resolve env vars from config and shell
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
const envFromConfigValue = this._workspaceConfigurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = (isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user);
const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...allowedEnvFromConfig }, lastActiveWorkspaceRoot);
const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
shellLaunchConfig.env = envFromShell;
terminalEnvironment.mergeEnvironments(env, envFromConfig);
terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');
// Adding other env keys necessary to create the process
terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables);
}
this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env);
this._process = this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty);
} }
this.processState = ProcessState.LAUNCHING; this.processState = ProcessState.LAUNCHING;
@@ -211,6 +171,50 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}, LAUNCHING_DURATION); }, LAUNCHING_DURATION);
} }
private _launchProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): ITerminalChildProcess {
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd);
// Compel type system as process.env should not have any undefined entries
let env: platform.IProcessEnvironment = {};
if (shellLaunchConfig.strictEnv) {
// Only base the terminal process environment on this environment and add the
// various mixins when strictEnv is false
env = { ...shellLaunchConfig.env } as any;
} else {
// Merge process env with the env from config and from shellLaunchConfig
env = { ...process.env } as any;
// Resolve env vars from config and shell
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
const envFromConfigValue = this._workspaceConfigurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = (isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user);
const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...allowedEnvFromConfig }, lastActiveWorkspaceRoot);
const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
shellLaunchConfig.env = envFromShell;
terminalEnvironment.mergeEnvironments(env, envFromConfig);
terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');
// Adding other env keys necessary to create the process
terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables);
}
this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env);
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty);
}
public setDimensions(cols: number, rows: number): void { public setDimensions(cols: number, rows: number): void {
if (!this._process) { if (!this._process) {
return; return;

View File

@@ -22,6 +22,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IFileService } from 'vs/platform/files/common/files'; import { IFileService } from 'vs/platform/files/common/files';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export abstract class TerminalService extends CommonTerminalService implements ITerminalService { export abstract class TerminalService extends CommonTerminalService implements ITerminalService {
protected _configHelper: IBrowserTerminalConfigHelper; protected _configHelper: IBrowserTerminalConfigHelper;
@@ -38,8 +39,9 @@ export abstract class TerminalService extends CommonTerminalService implements I
@IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService, @IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService,
@IExtensionService extensionService: IExtensionService, @IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService, @IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) { ) {
super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService); super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService);
} }
protected abstract _getDefaultShell(p: platform.Platform): string; protected abstract _getDefaultShell(p: platform.Platform): string;

View File

@@ -267,6 +267,7 @@ export interface ITerminalService {
*/ */
preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise<string>; preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise<string>;
extHostReady(remoteAuthority: string): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void; requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void;
} }

View File

@@ -7,7 +7,6 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy { export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
private _disposables: IDisposable[] = []; private _disposables: IDisposable[] = [];
@@ -44,15 +43,10 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
activeWorkspaceRootUri: URI, activeWorkspaceRootUri: URI,
cols: number, cols: number,
rows: number, rows: number,
@ITerminalService private readonly _terminalService: ITerminalService, @ITerminalService private readonly _terminalService: ITerminalService
@IExtensionService private readonly _extensionService: IExtensionService
) { ) {
this._extensionService.whenInstalledExtensionsRegistered().then(() => { this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
// TODO: MainThreadTerminalService is not ready at this point, fix this setTimeout(() => this._onProcessTitleChanged.fire('Starting...'), 0);
setTimeout(() => {
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
}, 0);
});
} }
public dispose(): void { public dispose(): void {

View File

@@ -19,6 +19,8 @@ import { IFileService } from 'vs/platform/files/common/files';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { isWindows } from 'vs/base/common/platform'; import { isWindows } from 'vs/base/common/platform';
import { basename } from 'vs/base/common/path'; import { basename } from 'vs/base/common/path';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { timeout } from 'vs/base/common/async';
export abstract class TerminalService implements ITerminalService { export abstract class TerminalService implements ITerminalService {
public _serviceBrand: any; public _serviceBrand: any;
@@ -32,7 +34,7 @@ export abstract class TerminalService implements ITerminalService {
return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), <ITerminalInstance[]>[]); return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), <ITerminalInstance[]>[]);
} }
private _findState: FindReplaceState; private _findState: FindReplaceState;
private _extHostsReady: { [authority: string]: boolean } = {};
private _activeTabIndex: number; private _activeTabIndex: number;
public get activeTabIndex(): number { return this._activeTabIndex; } public get activeTabIndex(): number { return this._activeTabIndex; }
@@ -65,12 +67,13 @@ export abstract class TerminalService implements ITerminalService {
constructor( constructor(
@IContextKeyService private readonly _contextKeyService: IContextKeyService, @IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IPanelService protected readonly _panelService: IPanelService, @IPanelService protected readonly _panelService: IPanelService,
@ILifecycleService lifecycleService: ILifecycleService, @ILifecycleService readonly lifecycleService: ILifecycleService,
@IStorageService protected readonly _storageService: IStorageService, @IStorageService protected readonly _storageService: IStorageService,
@INotificationService protected readonly _notificationService: INotificationService, @INotificationService protected readonly _notificationService: INotificationService,
@IDialogService private readonly _dialogService: IDialogService, @IDialogService private readonly _dialogService: IDialogService,
@IExtensionService private readonly _extensionService: IExtensionService, @IExtensionService private readonly _extensionService: IExtensionService,
@IFileService protected readonly _fileService: IFileService @IFileService protected readonly _fileService: IFileService,
@IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService
) { ) {
this._activeTabIndex = 0; this._activeTabIndex = 0;
this._isShuttingDown = false; this._isShuttingDown = false;
@@ -116,15 +119,22 @@ export abstract class TerminalService implements ITerminalService {
} }
public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void { public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void {
// Ensure extension host is ready before requesting a process this._extensionService.whenInstalledExtensionsRegistered().then(async () => {
this._extensionService.whenInstalledExtensionsRegistered().then(() => { // Wait for the remoteAuthority to be ready (and listening for events) before proceeding
// TODO: MainThreadTerminalService is not ready at this point, fix this const conn = this._remoteAgentService.getConnection();
setTimeout(() => { const remoteAuthority = conn ? conn.remoteAuthority : 'null';
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows }); let retries = 0;
}, 500); while (!this._extHostsReady[remoteAuthority] && ++retries < 50) {
await timeout(100);
}
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows });
}); });
} }
public extHostReady(remoteAuthority: string): void {
this._extHostsReady[remoteAuthority] = true;
}
private _onBeforeShutdown(): boolean | Promise<boolean> { private _onBeforeShutdown(): boolean | Promise<boolean> {
if (this.terminalInstances.length === 0) { if (this.terminalInstances.length === 0) {
// No terminal instances, don't veto // No terminal instances, don't veto

View File

@@ -28,6 +28,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { execFile } from 'child_process'; import { execFile } from 'child_process';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class TerminalService extends BrowserTerminalService implements ITerminalService { export class TerminalService extends BrowserTerminalService implements ITerminalService {
public get configHelper(): ITerminalConfigHelper { return this._configHelper; } public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
@@ -45,9 +46,10 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
@IDialogService dialogService: IDialogService, @IDialogService dialogService: IDialogService,
@IExtensionService extensionService: IExtensionService, @IExtensionService extensionService: IExtensionService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IFileService fileService: IFileService @IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) { ) {
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, environmentService, extensionService, fileService); super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, environmentService, extensionService, fileService, remoteAgentService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro); this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro);
ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => { ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => {

View File

@@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import * as modes from 'vs/editor/common/modes';
/** /**
* Set when the find widget in a webview is visible. * Set when the find widget in a webview is visible.
@@ -28,11 +29,6 @@ export interface IWebviewService {
): Webview; ): Webview;
} }
export interface WebviewPortMapping {
readonly port: number;
readonly resolvedPort: number;
}
export interface WebviewOptions { export interface WebviewOptions {
readonly allowSvgs?: boolean; readonly allowSvgs?: boolean;
readonly extension?: { readonly extension?: {
@@ -46,7 +42,7 @@ export interface WebviewContentOptions {
readonly allowScripts?: boolean; readonly allowScripts?: boolean;
readonly svgWhiteList?: string[]; readonly svgWhiteList?: string[];
readonly localResourceRoots?: ReadonlyArray<URI>; readonly localResourceRoots?: ReadonlyArray<URI>;
readonly portMappings?: ReadonlyArray<WebviewPortMapping>; readonly portMappings?: ReadonlyArray<modes.IWebviewPortMapping>;
} }
export interface Webview { export interface Webview {

View File

@@ -11,21 +11,22 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform'; import { isMacintosh } from 'vs/base/common/platform';
import { endsWith } from 'vs/base/common/strings'; import { endsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as modes from 'vs/editor/common/modes';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files'; import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService'; import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService';
import { WebviewFindWidget } from '../browser/webviewFindWidget'; import { WebviewFindWidget } from '../browser/webviewFindWidget';
import { WebviewContentOptions, WebviewPortMapping, WebviewOptions, Webview } from 'vs/workbench/contrib/webview/common/webview';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorOptions, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
export interface WebviewPortMapping { export interface WebviewPortMapping {
readonly port: number; readonly port: number;
@@ -153,7 +154,7 @@ class WebviewPortMappingProvider extends Disposable {
constructor( constructor(
session: WebviewSession, session: WebviewSession,
extensionLocation: URI | undefined, extensionLocation: URI | undefined,
mappings: () => ReadonlyArray<WebviewPortMapping>, mappings: () => ReadonlyArray<modes.IWebviewPortMapping>,
private readonly tunnelService: ITunnelService, private readonly tunnelService: ITunnelService,
extensionId: ExtensionIdentifier | undefined, extensionId: ExtensionIdentifier | undefined,
@ITelemetryService telemetryService: ITelemetryService @ITelemetryService telemetryService: ITelemetryService
@@ -183,23 +184,23 @@ class WebviewPortMappingProvider extends Disposable {
const port = +localhostMatch[1]; const port = +localhostMatch[1];
for (const mapping of mappings()) { for (const mapping of mappings()) {
if (mapping.port === port) { if (mapping.webviewPort === port) {
if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) {
const tunnel = await this.getOrCreateTunnel(mapping.resolvedPort); const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort);
if (tunnel) { if (tunnel) {
return { return {
redirectURL: details.url.replace( redirectURL: details.url.replace(
new RegExp(`^${uri.scheme}://localhost:${mapping.port}/`), new RegExp(`^${uri.scheme}://localhost:${mapping.webviewPort}/`),
`${uri.scheme}://localhost:${tunnel.tunnelLocalPort}/`) `${uri.scheme}://localhost:${tunnel.tunnelLocalPort}/`)
}; };
} }
} }
if (mapping.port !== mapping.resolvedPort) { if (mapping.webviewPort !== mapping.extensionHostPort) {
return { return {
redirectURL: details.url.replace( redirectURL: details.url.replace(
new RegExp(`^${uri.scheme}://localhost:${mapping.port}/`), new RegExp(`^${uri.scheme}://localhost:${mapping.webviewPort}/`),
`${uri.scheme}://localhost:${mapping.resolvedPort}/`) `${uri.scheme}://localhost:${mapping.extensionHostPort}/`)
}; };
} }
} }
@@ -416,7 +417,7 @@ export class WebviewElement extends Disposable implements Webview {
this._register(new WebviewPortMappingProvider( this._register(new WebviewPortMappingProvider(
session, session,
_options.extension ? _options.extension.location : undefined, _options.extension ? _options.extension.location : undefined,
() => (this._contentOptions.portMappings || [{ port: 3000, resolvedPort: 4000 }]), () => (this._contentOptions.portMappings || []),
tunnelService, tunnelService,
_options.extension ? _options.extension.id : undefined, _options.extension ? _options.extension.id : undefined,
telemetryService telemetryService

View File

@@ -72,8 +72,8 @@ export class FileDialogService implements IFileDialogService {
} }
} }
// ...then fallback to default folder path // ...then fallback to default file path
return this.defaultFolderPath(schemeFilter); return this.defaultFilePath(schemeFilter);
} }
private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions {

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 -2 16 16" enable-background="new -2 -2 16 16"><polygon fill="#C5C5C5" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>

Before

Width:  |  Height:  |  Size: 194 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#656565;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

Before

Width:  |  Height:  |  Size: 750 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon points="5.382,13 2.382,7 6.618,7 7,7.764 9.382,3 13.618,3 8.618,13" fill="#F6F6F6"/><path d="M12 4l-4 8h-2l-2-4h2l1 2 3-6h2z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 221 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{opacity:0;fill:#F0EFF1;} .icon-folder{fill:#C5C5C5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

Before

Width:  |  Height:  |  Size: 760 B

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import * as resources from 'vs/base/common/resources'; import * as resources from 'vs/base/common/resources';
import * as objects from 'vs/base/common/objects'; import * as objects from 'vs/base/common/objects';
import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files'; import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files';
import { IQuickInputService, IQuickPickItem, IQuickPick, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform'; import { isWindows } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
@@ -23,20 +23,26 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys'; import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys';
import { equalsIgnoreCase } from 'vs/base/common/strings'; import { equalsIgnoreCase, format } from 'vs/base/common/strings';
import { OpenLocalFileAction, OpenLocalFileFolderAction, OpenLocalFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
interface FileQuickPickItem extends IQuickPickItem { interface FileQuickPickItem extends IQuickPickItem {
uri: URI; uri: URI;
isFolder: boolean; isFolder: boolean;
} }
enum UpdateResult {
Updated,
NotUpdated,
InvalidPath
}
// Reference: https://en.wikipedia.org/wiki/Filename // Reference: https://en.wikipedia.org/wiki/Filename
const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g; const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i; const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
export class RemoteFileDialog { export class RemoteFileDialog {
private acceptButton: IQuickInputButton;
private fallbackListItem: FileQuickPickItem | undefined;
private options: IOpenDialogOptions; private options: IOpenDialogOptions;
private currentFolder: URI; private currentFolder: URI;
private filePickBox: IQuickPick<FileQuickPickItem>; private filePickBox: IQuickPick<FileQuickPickItem>;
@@ -52,6 +58,7 @@ export class RemoteFileDialog {
private autoCompletePathSegment: string; private autoCompletePathSegment: string;
private activeItem: FileQuickPickItem; private activeItem: FileQuickPickItem;
private userHome: URI; private userHome: URI;
private badPath: string | undefined;
constructor( constructor(
@IFileService private readonly fileService: IFileService, @IFileService private readonly fileService: IFileService,
@@ -64,8 +71,8 @@ export class RemoteFileDialog {
@IModeService private readonly modeService: IModeService, @IModeService private readonly modeService: IModeService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IContextKeyService contextKeyService: IContextKeyService @IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
) { ) {
this.remoteAuthority = this.environmentService.configuration.remoteAuthority; this.remoteAuthority = this.environmentService.configuration.remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService); this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
@@ -79,13 +86,6 @@ export class RemoteFileDialog {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
this.options = newOptions; this.options = newOptions;
const openFileString = nls.localize('remoteFileDialog.localFileFallback', '(Open Local File)');
const openFolderString = nls.localize('remoteFileDialog.localFolderFallback', '(Open Local Folder)');
const openFileFolderString = nls.localize('remoteFileDialog.localFileFolderFallback', '(Open Local File or Folder)');
let fallbackLabel = options.canSelectFiles ? (options.canSelectFolders ? openFileFolderString : openFileString) : openFolderString;
this.fallbackListItem = this.getFallbackFileSystem(fallbackLabel);
return this.pickResource(); return this.pickResource();
} }
@@ -100,7 +100,6 @@ export class RemoteFileDialog {
this.options = newOptions; this.options = newOptions;
this.options.canSelectFolders = true; this.options.canSelectFolders = true;
this.options.canSelectFiles = true; this.options.canSelectFiles = true;
this.fallbackListItem = this.getFallbackFileSystem(nls.localize('remoteFileDialog.localSaveFallback', '(Save Local File)'));
return new Promise<URI | undefined>((resolve) => { return new Promise<URI | undefined>((resolve) => {
this.pickResource(true).then(folderUri => { this.pickResource(true).then(folderUri => {
@@ -129,20 +128,13 @@ export class RemoteFileDialog {
private remoteUriFrom(path: string): URI { private remoteUriFrom(path: string): URI {
path = path.replace(/\\/g, '/'); path = path.replace(/\\/g, '/');
return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.remoteAuthority); return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.scheme === Schemas.file ? undefined : this.remoteAuthority);
} }
private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string { private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string {
return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file); return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file);
} }
private getFallbackFileSystem(label: string): FileQuickPickItem | undefined {
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
return { label: label, uri: URI.from({ scheme: this.options.availableFileSystems[1] }), isFolder: true };
}
return undefined;
}
private async getUserHome(): Promise<URI> { private async getUserHome(): Promise<URI> {
if (this.scheme !== Schemas.file) { if (this.scheme !== Schemas.file) {
const env = await this.remoteAgentService.getEnvironment(); const env = await this.remoteAgentService.getEnvironment();
@@ -182,33 +174,34 @@ export class RemoteFileDialog {
} }
} }
} }
this.acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: this.options.title };
return new Promise<URI | undefined>(async (resolve) => { return new Promise<URI | undefined>(async (resolve) => {
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>(); this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
this.filePickBox.matchOnLabel = false; this.filePickBox.matchOnLabel = false;
this.filePickBox.autoFocusOnList = false; this.filePickBox.autoFocusOnList = false;
this.filePickBox.ok = true;
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.filePickBox.customButton = true;
this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local');
const action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderAction : OpenLocalFileAction) : OpenLocalFolderAction;
const keybinding = this.keybindingService.lookupKeybinding(action.ID);
if (keybinding) {
const label = keybinding.getLabel();
if (label) {
this.filePickBox.customHover = format('{0} ({1})', action.LABEL, label);
}
}
}
let isResolving = false; let isResolving = false;
let isAcceptHandled = false; let isAcceptHandled = false;
this.currentFolder = homedir; this.currentFolder = homedir;
this.userEnteredPathSegment = ''; this.userEnteredPathSegment = '';
this.autoCompletePathSegment = ''; this.autoCompletePathSegment = '';
this.filePickBox.buttons = [this.acceptButton];
this.filePickBox.onDidTriggerButton(_ => {
// accept button
const resolveValue = this.addPostfix(this.remoteUriFrom(this.filePickBox.value));
this.validate(resolveValue).then(validated => {
if (validated) {
isResolving = true;
this.filePickBox.hide();
doResolve(this, resolveValue);
}
});
});
this.filePickBox.title = this.options.title; this.filePickBox.title = this.options.title;
this.filePickBox.value = this.pathFromUri(this.currentFolder); this.filePickBox.value = this.pathFromUri(this.currentFolder, true);
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
this.filePickBox.items = []; this.filePickBox.items = [];
function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) { function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) {
@@ -217,6 +210,28 @@ export class RemoteFileDialog {
dialog.filePickBox.dispose(); dialog.filePickBox.dispose();
} }
this.filePickBox.onDidCustom(() => {
if (isAcceptHandled || this.filePickBox.busy) {
return undefined; // {{SQL CARBON EDIT}} @todo anthonydresser return to return; when we do strict null checks
}
isAcceptHandled = true;
isResolving = true;
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.options.availableFileSystems.shift();
}
this.options.defaultUri = undefined;
if (this.requiresTrailing) {
return this.fileDialogService.showSaveDialog(this.options).then(result => {
doResolve(this, result);
});
} else {
return this.fileDialogService.showOpenDialog(this.options).then(result => {
doResolve(this, result ? result[0] : undefined);
});
}
});
this.filePickBox.onDidAccept(_ => { this.filePickBox.onDidAccept(_ => {
if (isAcceptHandled || this.filePickBox.busy) { if (isAcceptHandled || this.filePickBox.busy) {
return; return;
@@ -247,15 +262,16 @@ export class RemoteFileDialog {
this.filePickBox.onDidChangeValue(async value => { this.filePickBox.onDidChangeValue(async value => {
// onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything
if (this.isChangeFromUser()) { if (this.isChangeFromUser()) {
if (value !== this.constructFullUserPath()) { // If the user has just entered more bad path, don't change anything
if (value !== this.constructFullUserPath() && !this.isBadSubpath(value)) {
this.filePickBox.validationMessage = undefined; this.filePickBox.validationMessage = undefined;
this.shouldOverwriteFile = false; this.shouldOverwriteFile = false;
const valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value)); const valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value));
let isUpdate = false; let updated: UpdateResult = UpdateResult.NotUpdated;
if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), valueUri, true)) { if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), valueUri, true)) {
isUpdate = await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value)); updated = await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value));
} }
if (!isUpdate) { if (updated === UpdateResult.NotUpdated) {
this.setActiveItems(value); this.setActiveItems(value);
} }
} else { } else {
@@ -281,6 +297,10 @@ export class RemoteFileDialog {
}); });
} }
private isBadSubpath(value: string) {
return this.badPath && (value.length > this.badPath.length) && equalsIgnoreCase(value.substring(0, this.badPath.length), this.badPath);
}
private isChangeFromUser(): boolean { private isChangeFromUser(): boolean {
if ((this.filePickBox.value === this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment)) if ((this.filePickBox.value === this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment))
&& (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) { && (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) {
@@ -294,24 +314,6 @@ export class RemoteFileDialog {
} }
private async onDidAccept(): Promise<URI | undefined> { private async onDidAccept(): Promise<URI | undefined> {
// Check if Open Local has been selected
const selectedItems: ReadonlyArray<FileQuickPickItem> = this.filePickBox.selectedItems;
if (selectedItems && (selectedItems.length > 0) && (selectedItems[0] === this.fallbackListItem)) {
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.options.availableFileSystems.shift();
}
this.options.defaultUri = undefined;
if (this.requiresTrailing) {
return this.fileDialogService.showSaveDialog(this.options).then(result => {
return result;
});
} else {
return this.fileDialogService.showOpenDialog(this.options).then(result => {
return result ? result[0] : undefined;
});
}
}
let resolveValue: URI | undefined; let resolveValue: URI | undefined;
let navigateValue: URI | undefined; let navigateValue: URI | undefined;
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value; const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
@@ -360,10 +362,11 @@ export class RemoteFileDialog {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
private async tryUpdateItems(value: string, valueUri: URI): Promise<boolean> { private async tryUpdateItems(value: string, valueUri: URI): Promise<UpdateResult> {
if (value[value.length - 1] === '~') { if (value[value.length - 1] === '~') {
await this.updateItems(this.userHome); await this.updateItems(this.userHome);
return true; this.badPath = undefined;
return UpdateResult.Updated;
} else if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) { } else if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) {
let stat: IFileStat | undefined; let stat: IFileStat | undefined;
try { try {
@@ -373,7 +376,14 @@ export class RemoteFileDialog {
} }
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) { if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) {
await this.updateItems(valueUri); await this.updateItems(valueUri);
return true; return UpdateResult.Updated;
} else if (this.endsWithSlash(value)) {
// The input box contains a path that doesn't exist on the system.
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.');
// Save this bad path. It can take too long to to a stat on every user entered character, but once a user enters a bad path they are likely
// to keep typing more bad path. We can compare against this bad path and see if the user entered path starts with it.
this.badPath = value;
return UpdateResult.InvalidPath;
} else { } else {
const inputUriDirname = resources.dirname(valueUri); const inputUriDirname = resources.dirname(valueUri);
if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), inputUriDirname, true)) { if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), inputUriDirname, true)) {
@@ -385,12 +395,14 @@ export class RemoteFileDialog {
} }
if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) { if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) {
await this.updateItems(inputUriDirname, resources.basename(valueUri)); await this.updateItems(inputUriDirname, resources.basename(valueUri));
return true; this.badPath = undefined;
return UpdateResult.Updated;
} }
} }
} }
} }
return false; this.badPath = undefined;
return UpdateResult.NotUpdated;
} }
private setActiveItems(value: string) { private setActiveItems(value: string) {
@@ -428,7 +440,7 @@ export class RemoteFileDialog {
this.autoCompletePathSegment = ''; this.autoCompletePathSegment = '';
return false; return false;
} }
const itemBasename = (quickPickItem.label === '..') ? quickPickItem.label : resources.basename(quickPickItem.uri); const itemBasename = quickPickItem.label;
// Either force the autocomplete, or the old value should be one smaller than the new value and match the new value. // Either force the autocomplete, or the old value should be one smaller than the new value and match the new value.
if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) { if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) {
this.userEnteredPathSegment = startingBasename; this.userEnteredPathSegment = startingBasename;
@@ -582,8 +594,11 @@ export class RemoteFileDialog {
if (this.allowFolderSelection) { if (this.allowFolderSelection) {
this.filePickBox.activeItems = []; this.filePickBox.activeItems = [];
} }
this.filePickBox.valueSelection = [0, this.filePickBox.value.length]; if (!equalsIgnoreCase(this.filePickBox.value, newValue)) {
this.insertText(newValue, newValue); this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
this.insertText(newValue, newValue);
}
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
this.filePickBox.busy = false; this.filePickBox.busy = false;
}); });
} }
@@ -687,10 +702,6 @@ export class RemoteFileDialog {
if (backDir) { if (backDir) {
sorted.unshift(backDir); sorted.unshift(backDir);
} }
if (this.fallbackListItem) {
sorted.push(this.fallbackListItem);
}
return sorted; return sorted;
} }
@@ -724,11 +735,4 @@ export class RemoteFileDialog {
return undefined; return undefined;
} }
} }
private getDialogIcons(name: string): { light: URI, dark: URI } {
return {
dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/dark/${name}.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/light/${name}.svg`))
};
}
} }

View File

@@ -12,8 +12,8 @@ import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/c
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { IMessage } from 'vs/workbench/services/extensions/common/extensions'; import { IMessage } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { values } from 'vs/base/common/map';
const hasOwnProperty = Object.hasOwnProperty;
const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution); const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
export type ExtensionKind = 'workspace' | 'ui' | undefined; export type ExtensionKind = 'workspace' | 'ui' | undefined;
@@ -370,18 +370,14 @@ export interface IExtensionPointDescriptor {
export class ExtensionsRegistryImpl { export class ExtensionsRegistryImpl {
private _extensionPoints: { [extPoint: string]: ExtensionPoint<any>; }; private readonly _extensionPoints = new Map<string, ExtensionPoint<any>>();
constructor() {
this._extensionPoints = {};
}
public registerExtensionPoint<T>(desc: IExtensionPointDescriptor): IExtensionPoint<T> { public registerExtensionPoint<T>(desc: IExtensionPointDescriptor): IExtensionPoint<T> {
if (hasOwnProperty.call(this._extensionPoints, desc.extensionPoint)) { if (this._extensionPoints.has(desc.extensionPoint)) {
throw new Error('Duplicate extension point: ' + desc.extensionPoint); throw new Error('Duplicate extension point: ' + desc.extensionPoint);
} }
let result = new ExtensionPoint<T>(desc.extensionPoint, desc.defaultExtensionKind); const result = new ExtensionPoint<T>(desc.extensionPoint, desc.defaultExtensionKind);
this._extensionPoints[desc.extensionPoint] = result; this._extensionPoints.set(desc.extensionPoint, result);
schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema; schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema;
schemaRegistry.registerSchema(schemaId, schema); schemaRegistry.registerSchema(schemaId, schema);
@@ -390,11 +386,7 @@ export class ExtensionsRegistryImpl {
} }
public getExtensionPoints(): ExtensionPoint<any>[] { public getExtensionPoints(): ExtensionPoint<any>[] {
return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]); return values(this._extensionPoints);
}
public getExtensionPointsMap(): { [extPoint: string]: ExtensionPoint<any>; } {
return this._extensionPoints;
} }
} }

View File

@@ -20,6 +20,9 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; import { Dialog } from 'vs/base/browser/ui/dialog/dialog';
import { attachDialogStyler } from 'vs/platform/theme/common/styler'; import { attachDialogStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EventHelper } from 'vs/base/browser/dom';
export class ProgressService2 implements IProgressService2 { export class ProgressService2 implements IProgressService2 {
@@ -34,7 +37,8 @@ export class ProgressService2 implements IProgressService2 {
@INotificationService private readonly _notificationService: INotificationService, @INotificationService private readonly _notificationService: INotificationService,
@IStatusbarService private readonly _statusbarService: IStatusbarService, @IStatusbarService private readonly _statusbarService: IStatusbarService,
@ILayoutService private readonly _layoutService: ILayoutService, @ILayoutService private readonly _layoutService: ILayoutService,
@IThemeService private readonly _themeService: IThemeService @IThemeService private readonly _themeService: IThemeService,
@IKeybindingService private readonly _keybindingService: IKeybindingService
) { } ) { }
withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> { withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> {
@@ -276,6 +280,10 @@ export class ProgressService2 implements IProgressService2 {
private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
const disposables: IDisposable[] = []; const disposables: IDisposable[] = [];
const allowableCommands = [
'workbench.action.quit',
'workbench.action.reloadWindow'
];
let dialog: Dialog; let dialog: Dialog;
@@ -284,7 +292,17 @@ export class ProgressService2 implements IProgressService2 {
this._layoutService.container, this._layoutService.container,
message, message,
[options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")],
{ type: 'pending' } {
type: 'pending',
keyEventProcessor: (event: StandardKeyboardEvent) => {
const resolved = this._keybindingService.softDispatch(event, this._layoutService.container);
if (resolved && resolved.commandId) {
if (allowableCommands.indexOf(resolved.commandId) === -1) {
EventHelper.stop(event, true);
}
}
}
}
); );
disposables.push(dialog); disposables.push(dialog);

View File

@@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { connectRemoteAgentManagement, IConnectionOptions, IWebSocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { connectRemoteAgentManagement, IConnectionOptions, IWebSocketFactory, PersistenConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
@@ -73,15 +73,18 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon
private readonly _onReconnecting = this._register(new Emitter<void>()); private readonly _onReconnecting = this._register(new Emitter<void>());
public readonly onReconnecting = this._onReconnecting.event; public readonly onReconnecting = this._onReconnecting.event;
private readonly _onDidStateChange = this._register(new Emitter<PersistenConnectionEvent>());
public readonly onDidStateChange = this._onDidStateChange.event;
readonly remoteAuthority: string; readonly remoteAuthority: string;
private _connection: Promise<Client<RemoteAgentConnectionContext>> | null; private _connection: Promise<Client<RemoteAgentConnectionContext>> | null;
constructor( constructor(
remoteAuthority: string, remoteAuthority: string,
private _commit: string | undefined, private readonly _commit: string | undefined,
private _webSocketFactory: IWebSocketFactory, private readonly _webSocketFactory: IWebSocketFactory,
private _environmentService: IEnvironmentService, private readonly _environmentService: IEnvironmentService,
private _remoteAuthorityResolverService: IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService
) { ) {
super(); super();
this.remoteAuthority = remoteAuthority; this.remoteAuthority = remoteAuthority;
@@ -121,8 +124,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon
} }
} }
}; };
const connection = await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`); const connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`));
this._register(connection); this._register(connection.onDidStateChange(e => this._onDidStateChange.fire(e)));
return connection.client; return connection.client;
} }
} }

View File

@@ -8,6 +8,7 @@ import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platfo
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { PersistenConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
export const RemoteExtensionLogFileName = 'remoteagent'; export const RemoteExtensionLogFileName = 'remoteagent';
@@ -25,7 +26,9 @@ export interface IRemoteAgentService {
export interface IRemoteAgentConnection { export interface IRemoteAgentConnection {
readonly remoteAuthority: string; readonly remoteAuthority: string;
readonly onReconnecting: Event<void>;
readonly onDidStateChange: Event<PersistenConnectionEvent>;
getChannel<T extends IChannel>(channelName: string): T; getChannel<T extends IChannel>(channelName: string): T;
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void; registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void;
onReconnecting: Event<void>;
} }