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"
},
"main": "./out/extension",
"extensionKind": "ui",
"categories": [
"Programming Languages"
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -63,7 +63,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
<label class="caption" for="includeSystemInfo">${escape(localize({
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>
<div class="block-info hidden">
<!-- To be dynamically filled -->
@@ -73,7 +73,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
<label class="caption" for="includeProcessInfo">${escape(localize({
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>
<pre class="block-info hidden">
<code>
@@ -85,7 +85,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
<label class="caption" for="includeWorkspaceInfo">${escape(localize({
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>
<pre id="systemInfo" class="block-info hidden">
<code>
@@ -97,7 +97,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeExtensions" checked/>
<label class="caption" for="includeExtensions">${escape(localize({
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>
<div id="systemInfo" class="block-info hidden">
<!-- To be dynamically filled -->
@@ -107,7 +107,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
<label class="caption" for="includeSearchedExtensions">${escape(localize({
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>
<div class="block-info hidden">
<!-- To be dynamically filled -->
@@ -117,7 +117,7 @@ export default (): string => `
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
<label class="caption" for="includeSettingsSearchDetails">${escape(localize({
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>
<div class="block-info hidden">
<!-- 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)
// 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.setHeight(1);

View File

@@ -344,7 +344,7 @@ export class TextAreaInput extends Disposable {
//
// 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 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 tabbing into the textarea
// * 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>;
}
/**
* @internal
*/
export interface IWebviewPortMapping {
webviewPort: number;
extensionHostPort: number;
}
/**
* @internal
*/
@@ -1415,7 +1423,7 @@ export interface IWebviewOptions {
readonly enableScripts?: boolean;
readonly enableCommandUris?: boolean;
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 { attachDialogStyler } from 'vs/platform/theme/common/styler';
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 {
_serviceBrand: any;
@@ -76,7 +78,10 @@ export class DialogService implements IDialogService {
{
detail: options ? options.detail : undefined,
cancelId: options ? options.cancelId : undefined,
type: this.getDialogType(severity)
type: this.getDialogType(severity),
keyEventProcessor: (event: StandardKeyboardEvent) => {
EventHelper.stop(event, true);
}
});
dialogDisposables.push(dialog);

View File

@@ -206,12 +206,14 @@ export function buildHelpMessage(productName: string, executableName: string, ve
help.push('');
help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`);
help.push('');
if (os.platform() === 'win32') {
help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", executableName));
} else {
help.push(localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", executableName));
if (isPipeSupported) {
if (os.platform() === 'win32') {
help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {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) {
let categoryOptions = options.filter(o => !!o.description && o.cat === key && isOptionSupported(o));
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 {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const configuredUIExtensions = configurationService.getValue<string[]>('_workbench.uiExtensions') || [];
if (configuredUIExtensions.length) {
if (configuredUIExtensions.indexOf(extensionId) !== -1) {
return true;
}
if (configuredUIExtensions.indexOf(`-${extensionId}`) !== -1) {
return false;
}
if (configuredUIExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return true;
}
if (configuredUIExtensions.some(id => areSameExtensions({ id }, { id: `-${extensionId}` }))) {
return false;
}
switch (manifest.extensionKind) {
case 'ui': return true;

View File

@@ -162,6 +162,16 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
readonly onDidAccept: Event<void>;
ok: boolean;
readonly onDidCustom: Event<void>;
customButton: boolean;
customLabel: string;
customHover: string;
buttons: ReadonlyArray<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 { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
export const enum ConnectionType {
Management = 1,
@@ -147,8 +148,38 @@ export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunn
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 {
private readonly _onDidStateChange = this._register(new Emitter<PersistenConnectionEvent>());
public readonly onDidStateChange = this._onDidStateChange.event;
protected readonly _options: IConnectionOptions;
public readonly reconnectionToken: string;
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>;
}
/**
* 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.
*/
@@ -5740,6 +5755,18 @@ declare module 'vscode' {
* Pass in an empty array to disallow access to any local resources.
*/
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;
}
//#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) {
this._proxy.$acceptActiveTerminalChanged(activeInstance.id);
}
this.terminalService.extHostReady(extHostContext.remoteAuthority);
}
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 { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
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 { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -45,9 +45,9 @@ export class MainThreadWindow implements MainThreadWindowShape {
return this.windowService.isFocused();
}
async $openUri(uriComponent: UriComponents): Promise<boolean> {
async $openUri(uriComponent: UriComponents, options: IOpenUriOptions): Promise<boolean> {
const uri = URI.revive(uriComponent);
if (!!this.environmentService.configuration.remoteAuthority) {
if (options.allowTunneling && !!this.environmentService.configuration.remoteAuthority) {
if (uri.scheme === 'http' || uri.scheme === 'https') {
const port = this.getLocalhostPort(uri);
if (typeof port === 'number') {

View File

@@ -539,7 +539,7 @@ export interface ExtHostWebviewsShape {
$onMessage(handle: WebviewPanelHandle, message: any): void;
$onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, newState: WebviewPanelViewState): 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 {
@@ -689,9 +689,13 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise<void>;
}
export interface IOpenUriOptions {
readonly allowTunneling?: boolean;
}
export interface MainThreadWindowShape extends IDisposable {
$getWindowVisibility(): Promise<boolean>;
$openUri(uri: UriComponents): Promise<boolean>;
$openUri(uri: UriComponents, options: IOpenUriOptions): Promise<boolean>;
}
// -- 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 { Disposable } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as modes from 'vs/editor/common/modes';
type IconPath = URI | { light: URI, dark: URI };
@@ -58,7 +59,7 @@ export class ExtHostWebview implements vscode.Webview {
public set options(newOptions: vscode.WebviewOptions) {
this.assertNotDisposed();
this._proxy.$setOptions(this._handle, newOptions);
this._proxy.$setOptions(this._handle, convertWebviewOptions(newOptions));
this._options = newOptions;
}
@@ -257,7 +258,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
};
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 panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview);
@@ -325,7 +326,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
title: string,
state: any,
position: EditorViewColumn,
options: vscode.WebviewOptions & vscode.WebviewPanelOptions
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
): Promise<void> {
const serializer = this._serializers.get(viewType);
if (!serializer) {
@@ -342,3 +343,20 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
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 { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext } from './extHost.protocol';
import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext, IOpenUriOptions } from './extHost.protocol';
import { WindowState } from 'vscode';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
@@ -38,7 +38,7 @@ export class ExtHostWindow implements ExtHostWindowShape {
this._onDidChangeWindowState.fire(this._state);
}
openUri(stringOrUri: string | URI): Promise<boolean> {
openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise<boolean> {
if (typeof stringOrUri === 'string') {
try {
stringOrUri = URI.parse(stringOrUri);
@@ -51,6 +51,6 @@ export class ExtHostWindow implements ExtHostWindowShape {
} else if (stringOrUri.scheme === Schemas.command) {
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 { URI, UriComponents } from 'vs/base/common/uri';
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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -122,7 +122,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
$createWebviewCodeInset(
handle: WebviewInsetHandle,
symbolId: string,
options: IWebviewOptions,
options: modes.IWebviewOptions,
extensionId: ExtensionIdentifier,
extensionLocation: UriComponents
): 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') {
this.getWebviewElement(handle).options = reviveWebviewOptions(options as any /*todo@mat */);
} else {

View File

@@ -257,7 +257,7 @@ export function createApiFactory(
return extHostClipboard;
},
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);
}
if (uri.scheme === 'http' || uri.scheme === 'https') {
return mainThreadWindow.$openUri(uri);
return mainThreadWindow.$openUri(uri, { allowTunneling: true });
} else if (uri.scheme === 'mailto') {
return mainThreadWindow.$openUri(uri);
return mainThreadWindow.$openUri(uri, {});
}
return this.callOriginal(target, options);
};

View File

@@ -68,9 +68,11 @@ interface QuickInputUI {
visibleCount: CountBadge;
count: CountBadge;
message: HTMLElement;
customButton: Button;
progressBar: ProgressBar;
list: QuickInputList;
onDidAccept: Event<void>;
onDidCustom: Event<void>;
onDidTriggerButton: Event<IQuickInputButton>;
ignoreFocusOut: boolean;
keyMods: Writeable<IKeyMods>;
@@ -92,6 +94,7 @@ type Visibilities = {
message?: boolean;
list?: boolean;
ok?: boolean;
customButton?: boolean;
};
class QuickInput implements IQuickInput {
@@ -312,6 +315,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _placeholder: string;
private onDidChangeValueEmitter = new Emitter<string>();
private onDidAcceptEmitter = new Emitter<void>();
private onDidCustomEmitter = new Emitter<void>();
private _items: Array<T | IQuickPickSeparator> = [];
private itemsUpdated = false;
private _canSelectMany = false;
@@ -331,6 +335,10 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _valueSelection: Readonly<[number, number]>;
private valueSelectionUpdated = true;
private _validationMessage: string;
private _ok: boolean;
private _customButton: boolean;
private _customButtonLabel: string;
private _customButtonHover: string;
quickNavigate: IQuickNavigateConfiguration;
@@ -339,6 +347,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.disposables.push(
this.onDidChangeValueEmitter,
this.onDidAcceptEmitter,
this.onDidCustomEmitter,
this.onDidChangeActiveEmitter,
this.onDidChangeSelectionEmitter,
this.onDidTriggerItemButtonEmitter,
@@ -367,6 +376,8 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
onDidAccept = this.onDidAcceptEmitter.event;
onDidCustom = this.onDidCustomEmitter.event;
get items() {
return this._items;
}
@@ -463,6 +474,42 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
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 {
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.ui.onDidCustom(() => {
this.onDidCustomEmitter.fire(undefined);
}),
this.ui.list.onDidChangeFocus(focusedItems => {
if (this.activeItemsUpdated) {
return; // Expect another event.
@@ -697,12 +747,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.message.textContent = null;
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.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.setComboboxAccessibility(true);
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 okContainer: HTMLElement;
private ok: Button;
private customButtonContainer: HTMLElement;
private ui: QuickInputUI;
private comboboxAccessibility = false;
private enabled = true;
@@ -855,6 +908,7 @@ export class QuickInputService extends Component implements IQuickInputService {
private inQuickOpenContext: IContextKey<boolean>;
private contexts: { [id: string]: IContextKey<boolean>; } = Object.create(null);
private onDidAcceptEmitter = this._register(new Emitter<void>());
private onDidCustomEmitter = this._register(new Emitter<void>());
private onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false };
@@ -1013,6 +1067,14 @@ export class QuickInputService extends Component implements IQuickInputService {
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 progressBar = new ProgressBar(container);
@@ -1098,9 +1160,11 @@ export class QuickInputService extends Component implements IQuickInputService {
visibleCount,
count,
message,
customButton,
progressBar,
list,
onDidAccept: this.onDidAcceptEmitter.event,
onDidCustom: this.onDidCustomEmitter.event,
onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
ignoreFocusOut: false,
keyMods: this.keyMods,
@@ -1330,6 +1394,7 @@ export class QuickInputService extends Component implements IQuickInputService {
this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none';
this.countContainer.style.display = visibilities.count ? '' : '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.list.display(!!visibilities.list);
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 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
.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;
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);
}
for (const viewDescriptor of toSetVisible) {
viewDescriptor.repository.setSelected(true);
this.viewsModel.setVisible(viewDescriptor.id, true, size);
}
}

View File

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

View File

@@ -433,12 +433,10 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneRightT
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, {
primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, {
primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category);
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);
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
} else {
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);
this._process = this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty);
this._process = this._launchProcess(shellLaunchConfig, cols, rows);
}
this.processState = ProcessState.LAUNCHING;
@@ -211,6 +171,50 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}, 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 {
if (!this._process) {
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 { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
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 {
protected _configHelper: IBrowserTerminalConfigHelper;
@@ -38,8 +39,9 @@ export abstract class TerminalService extends CommonTerminalService implements I
@IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService,
@IExtensionService extensionService: IExtensionService,
@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;

View File

@@ -267,6 +267,7 @@ export interface ITerminalService {
*/
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;
}

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 { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
private _disposables: IDisposable[] = [];
@@ -44,15 +43,10 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
activeWorkspaceRootUri: URI,
cols: number,
rows: number,
@ITerminalService private readonly _terminalService: ITerminalService,
@IExtensionService private readonly _extensionService: IExtensionService
@ITerminalService private readonly _terminalService: ITerminalService
) {
this._extensionService.whenInstalledExtensionsRegistered().then(() => {
// TODO: MainThreadTerminalService is not ready at this point, fix this
setTimeout(() => {
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
}, 0);
});
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
setTimeout(() => this._onProcessTitleChanged.fire('Starting...'), 0);
}
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 { isWindows } from 'vs/base/common/platform';
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 {
public _serviceBrand: any;
@@ -32,7 +34,7 @@ export abstract class TerminalService implements ITerminalService {
return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), <ITerminalInstance[]>[]);
}
private _findState: FindReplaceState;
private _extHostsReady: { [authority: string]: boolean } = {};
private _activeTabIndex: number;
public get activeTabIndex(): number { return this._activeTabIndex; }
@@ -65,12 +67,13 @@ export abstract class TerminalService implements ITerminalService {
constructor(
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IPanelService protected readonly _panelService: IPanelService,
@ILifecycleService lifecycleService: ILifecycleService,
@ILifecycleService readonly lifecycleService: ILifecycleService,
@IStorageService protected readonly _storageService: IStorageService,
@INotificationService protected readonly _notificationService: INotificationService,
@IDialogService private readonly _dialogService: IDialogService,
@IExtensionService private readonly _extensionService: IExtensionService,
@IFileService protected readonly _fileService: IFileService
@IFileService protected readonly _fileService: IFileService,
@IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService
) {
this._activeTabIndex = 0;
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 {
// Ensure extension host is ready before requesting a process
this._extensionService.whenInstalledExtensionsRegistered().then(() => {
// TODO: MainThreadTerminalService is not ready at this point, fix this
setTimeout(() => {
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows });
}, 500);
this._extensionService.whenInstalledExtensionsRegistered().then(async () => {
// Wait for the remoteAuthority to be ready (and listening for events) before proceeding
const conn = this._remoteAgentService.getConnection();
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
let retries = 0;
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> {
if (this.terminalInstances.length === 0) {
// 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 { execFile } from 'child_process';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class TerminalService extends BrowserTerminalService implements ITerminalService {
public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
@@ -45,9 +46,10 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
@IDialogService dialogService: IDialogService,
@IExtensionService extensionService: IExtensionService,
@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);
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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
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.
@@ -28,11 +29,6 @@ export interface IWebviewService {
): Webview;
}
export interface WebviewPortMapping {
readonly port: number;
readonly resolvedPort: number;
}
export interface WebviewOptions {
readonly allowSvgs?: boolean;
readonly extension?: {
@@ -46,7 +42,7 @@ export interface WebviewContentOptions {
readonly allowScripts?: boolean;
readonly svgWhiteList?: string[];
readonly localResourceRoots?: ReadonlyArray<URI>;
readonly portMappings?: ReadonlyArray<WebviewPortMapping>;
readonly portMappings?: ReadonlyArray<modes.IWebviewPortMapping>;
}
export interface Webview {

View File

@@ -11,21 +11,22 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import { endsWith } from 'vs/base/common/strings';
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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
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 { 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 { 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 { 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 {
readonly port: number;
@@ -153,7 +154,7 @@ class WebviewPortMappingProvider extends Disposable {
constructor(
session: WebviewSession,
extensionLocation: URI | undefined,
mappings: () => ReadonlyArray<WebviewPortMapping>,
mappings: () => ReadonlyArray<modes.IWebviewPortMapping>,
private readonly tunnelService: ITunnelService,
extensionId: ExtensionIdentifier | undefined,
@ITelemetryService telemetryService: ITelemetryService
@@ -183,23 +184,23 @@ class WebviewPortMappingProvider extends Disposable {
const port = +localhostMatch[1];
for (const mapping of mappings()) {
if (mapping.port === port) {
if (mapping.webviewPort === port) {
if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) {
const tunnel = await this.getOrCreateTunnel(mapping.resolvedPort);
const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort);
if (tunnel) {
return {
redirectURL: details.url.replace(
new RegExp(`^${uri.scheme}://localhost:${mapping.port}/`),
new RegExp(`^${uri.scheme}://localhost:${mapping.webviewPort}/`),
`${uri.scheme}://localhost:${tunnel.tunnelLocalPort}/`)
};
}
}
if (mapping.port !== mapping.resolvedPort) {
if (mapping.webviewPort !== mapping.extensionHostPort) {
return {
redirectURL: details.url.replace(
new RegExp(`^${uri.scheme}://localhost:${mapping.port}/`),
`${uri.scheme}://localhost:${mapping.resolvedPort}/`)
new RegExp(`^${uri.scheme}://localhost:${mapping.webviewPort}/`),
`${uri.scheme}://localhost:${mapping.extensionHostPort}/`)
};
}
}
@@ -416,7 +417,7 @@ export class WebviewElement extends Disposable implements Webview {
this._register(new WebviewPortMappingProvider(
session,
_options.extension ? _options.extension.location : undefined,
() => (this._contentOptions.portMappings || [{ port: 3000, resolvedPort: 4000 }]),
() => (this._contentOptions.portMappings || []),
tunnelService,
_options.extension ? _options.extension.id : undefined,
telemetryService

View File

@@ -72,8 +72,8 @@ export class FileDialogService implements IFileDialogService {
}
}
// ...then fallback to default folder path
return this.defaultFolderPath(schemeFilter);
// ...then fallback to default file path
return this.defaultFilePath(schemeFilter);
}
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 objects from 'vs/base/common/objects';
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 { isWindows } from 'vs/base/common/platform';
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 { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
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 {
uri: URI;
isFolder: boolean;
}
enum UpdateResult {
Updated,
NotUpdated,
InvalidPath
}
// Reference: https://en.wikipedia.org/wiki/Filename
const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
export class RemoteFileDialog {
private acceptButton: IQuickInputButton;
private fallbackListItem: FileQuickPickItem | undefined;
private options: IOpenDialogOptions;
private currentFolder: URI;
private filePickBox: IQuickPick<FileQuickPickItem>;
@@ -52,6 +58,7 @@ export class RemoteFileDialog {
private autoCompletePathSegment: string;
private activeItem: FileQuickPickItem;
private userHome: URI;
private badPath: string | undefined;
constructor(
@IFileService private readonly fileService: IFileService,
@@ -64,8 +71,8 @@ export class RemoteFileDialog {
@IModeService private readonly modeService: IModeService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IContextKeyService contextKeyService: IContextKeyService
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
this.remoteAuthority = this.environmentService.configuration.remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
@@ -79,13 +86,6 @@ export class RemoteFileDialog {
return Promise.resolve(undefined);
}
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();
}
@@ -100,7 +100,6 @@ export class RemoteFileDialog {
this.options = newOptions;
this.options.canSelectFolders = true;
this.options.canSelectFiles = true;
this.fallbackListItem = this.getFallbackFileSystem(nls.localize('remoteFileDialog.localSaveFallback', '(Save Local File)'));
return new Promise<URI | undefined>((resolve) => {
this.pickResource(true).then(folderUri => {
@@ -129,20 +128,13 @@ export class RemoteFileDialog {
private remoteUriFrom(path: string): URI {
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 {
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> {
if (this.scheme !== Schemas.file) {
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) => {
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
this.filePickBox.matchOnLabel = 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 isAcceptHandled = false;
this.currentFolder = homedir;
this.userEnteredPathSegment = '';
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.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 = [];
function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) {
@@ -217,6 +210,28 @@ export class RemoteFileDialog {
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(_ => {
if (isAcceptHandled || this.filePickBox.busy) {
return;
@@ -247,15 +262,16 @@ export class RemoteFileDialog {
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
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.shouldOverwriteFile = false;
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)) {
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);
}
} 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 {
if ((this.filePickBox.value === this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment))
&& (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) {
@@ -294,24 +314,6 @@ export class RemoteFileDialog {
}
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 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;
@@ -360,10 +362,11 @@ export class RemoteFileDialog {
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] === '~') {
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))) {
let stat: IFileStat | undefined;
try {
@@ -373,7 +376,14 @@ export class RemoteFileDialog {
}
if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) {
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 {
const inputUriDirname = resources.dirname(valueUri);
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) !== '.')) {
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) {
@@ -428,7 +440,7 @@ export class RemoteFileDialog {
this.autoCompletePathSegment = '';
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.
if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) {
this.userEnteredPathSegment = startingBasename;
@@ -582,8 +594,11 @@ export class RemoteFileDialog {
if (this.allowFolderSelection) {
this.filePickBox.activeItems = [];
}
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
this.insertText(newValue, newValue);
if (!equalsIgnoreCase(this.filePickBox.value, 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;
});
}
@@ -687,10 +702,6 @@ export class RemoteFileDialog {
if (backDir) {
sorted.unshift(backDir);
}
if (this.fallbackListItem) {
sorted.push(this.fallbackListItem);
}
return sorted;
}
@@ -724,11 +735,4 @@ export class RemoteFileDialog {
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 { IMessage } from 'vs/workbench/services/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);
export type ExtensionKind = 'workspace' | 'ui' | undefined;
@@ -370,18 +370,14 @@ export interface IExtensionPointDescriptor {
export class ExtensionsRegistryImpl {
private _extensionPoints: { [extPoint: string]: ExtensionPoint<any>; };
constructor() {
this._extensionPoints = {};
}
private readonly _extensionPoints = new Map<string, ExtensionPoint<any>>();
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);
}
let result = new ExtensionPoint<T>(desc.extensionPoint, desc.defaultExtensionKind);
this._extensionPoints[desc.extensionPoint] = result;
const result = new ExtensionPoint<T>(desc.extensionPoint, desc.defaultExtensionKind);
this._extensionPoints.set(desc.extensionPoint, result);
schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema;
schemaRegistry.registerSchema(schemaId, schema);
@@ -390,11 +386,7 @@ export class ExtensionsRegistryImpl {
}
public getExtensionPoints(): ExtensionPoint<any>[] {
return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]);
}
public getExtensionPointsMap(): { [extPoint: string]: ExtensionPoint<any>; } {
return this._extensionPoints;
return values(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 { attachDialogStyler } from 'vs/platform/theme/common/styler';
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 {
@@ -34,7 +37,8 @@ export class ProgressService2 implements IProgressService2 {
@INotificationService private readonly _notificationService: INotificationService,
@IStatusbarService private readonly _statusbarService: IStatusbarService,
@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> {
@@ -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 {
const disposables: IDisposable[] = [];
const allowableCommands = [
'workbench.action.quit',
'workbench.action.reloadWindow'
];
let dialog: Dialog;
@@ -284,7 +292,17 @@ export class ProgressService2 implements IProgressService2 {
this._layoutService.container,
message,
[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);

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 { Client } from 'vs/base/parts/ipc/common/ipc.net';
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 { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
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>());
public readonly onReconnecting = this._onReconnecting.event;
private readonly _onDidStateChange = this._register(new Emitter<PersistenConnectionEvent>());
public readonly onDidStateChange = this._onDidStateChange.event;
readonly remoteAuthority: string;
private _connection: Promise<Client<RemoteAgentConnectionContext>> | null;
constructor(
remoteAuthority: string,
private _commit: string | undefined,
private _webSocketFactory: IWebSocketFactory,
private _environmentService: IEnvironmentService,
private _remoteAuthorityResolverService: IRemoteAuthorityResolverService
private readonly _commit: string | undefined,
private readonly _webSocketFactory: IWebSocketFactory,
private readonly _environmentService: IEnvironmentService,
private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.remoteAuthority = remoteAuthority;
@@ -121,8 +124,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon
}
}
};
const connection = await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`);
this._register(connection);
const connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`));
this._register(connection.onDidStateChange(e => this._onDidStateChange.fire(e)));
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 { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { Event } from 'vs/base/common/event';
import { PersistenConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
export const RemoteExtensionLogFileName = 'remoteagent';
@@ -25,7 +26,9 @@ export interface IRemoteAgentService {
export interface IRemoteAgentConnection {
readonly remoteAuthority: string;
readonly onReconnecting: Event<void>;
readonly onDidStateChange: Event<PersistenConnectionEvent>;
getChannel<T extends IChannel>(channelName: string): T;
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void;
onReconnecting: Event<void>;
}