Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3 (#5587)

* Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3

* pipeline errors

* fix build
This commit is contained in:
Anthony Dresser
2019-05-23 11:16:03 -07:00
committed by GitHub
parent ca36f20c6b
commit cf8f8907ee
141 changed files with 6450 additions and 1228 deletions
+113 -56
View File
@@ -1,5 +1,6 @@
/**
* Copyright (c) 2017, Daniel Imms (Source EULA).
* Copyright (c) 2018, Microsoft Corporation (Source EULA).
*/
declare module 'node-pty' {
@@ -11,76 +12,132 @@ declare module 'node-pty' {
* escaped properly.
* @param options The options of the terminal.
* @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
* @see Parsing C++ Command-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
* @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
* @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
*/
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions): IPty;
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
export interface IPtyForkOptions {
name?: string;
cols?: number;
rows?: number;
cwd?: string;
env?: { [key: string]: string };
uid?: number;
gid?: number;
encoding?: string;
/**
* Whether to use the experimental ConPTY system on Windows. This setting will be ignored on
* non-Windows.
*/
experimentalUseConpty?: boolean;
name?: string;
cols?: number;
rows?: number;
cwd?: string;
env?: { [key: string]: string };
uid?: number;
gid?: number;
encoding?: string;
}
export interface IWindowsPtyForkOptions {
name?: string;
cols?: number;
rows?: number;
cwd?: string;
env?: { [key: string]: string };
encoding?: string;
/**
* Whether to use the experimental ConPTY system on Windows. When this is not set, ConPTY will
* be used when the Windows build number is >= 18309 (it's available in 17134 and 17692 but is
* too unstable to enable by default).
*
* This setting does nothing on non-Windows.
*/
experimentalUseConpty?: boolean;
/**
* Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
* @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
*/
conptyInheritCursor?: boolean;
}
/**
* An interface representing a pseudoterminal, on Windows this is emulated via the winpty library.
*/
export interface IPty {
/**
* The process ID of the outer process.
*/
pid: number;
/**
* The process ID of the outer process.
*/
readonly pid: number;
/**
* The title of the active process.
*/
process: string;
/**
* The column size in characters.
*/
readonly cols: number;
/**
* Adds a listener to the data event, fired when data is returned from the pty.
* @param event The name of the event.
* @param listener The callback function.
*/
on(event: 'data', listener: (data: string) => void): void;
/**
* The row size in characters.
*/
readonly rows: number;
/**
* Adds a listener to the exit event, fired when the pty exits.
* @param event The name of the event.
* @param listener The callback function, exitCode is the exit code of the process and signal is
* the signal that triggered the exit. signal is not supported on Windows.
*/
on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;
/**
* The title of the active process.
*/
readonly process: string;
/**
* Resizes the dimensions of the pty.
* @param columns The number of columns to use.
* @param rows The number of rows to use.
*/
resize(columns: number, rows: number): void;
/**
* Adds an event listener for when a data event fires. This happens when data is returned from
* the pty.
* @returns an `IDisposable` to stop listening.
*/
readonly onData: IEvent<string>;
/**
* Writes data to the pty.
* @param data The data to write.
*/
write(data: string): void;
/**
* Adds an event listener for when an exit event fires. This happens when the pty exits.
* @returns an `IDisposable` to stop listening.
*/
readonly onExit: IEvent<{ exitCode: number, signal?: number }>;
/**
* Kills the pty.
* @param signal The signal to use, defaults to SIGHUP. If the TIOCSIG/TIOCSIGNAL ioctl is not
* supported then the process will be killed instead. This parameter is not supported on
* Windows.
* @throws Will throw when signal is used on Windows.
*/
kill(signal?: string): void;
/**
* Adds a listener to the data event, fired when data is returned from the pty.
* @param event The name of the event.
* @param listener The callback function.
* @deprecated Use IPty.onData
*/
on(event: 'data', listener: (data: string) => void): void;
/**
* Adds a listener to the exit event, fired when the pty exits.
* @param event The name of the event.
* @param listener The callback function, exitCode is the exit code of the process and signal is
* the signal that triggered the exit. signal is not supported on Windows.
* @deprecated Use IPty.onExit
*/
on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;
/**
* Resizes the dimensions of the pty.
* @param columns THe number of columns to use.
* @param rows The number of rows to use.
*/
resize(columns: number, rows: number): void;
/**
* Writes data to the pty.
* @param data The data to write.
*/
write(data: string): void;
/**
* Kills the pty.
* @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on
* Windows.
* @throws Will throw when signal is used on Windows.
*/
kill(signal?: string): void;
}
}
/**
* An object that can be disposed via a dispose function.
*/
export interface IDisposable {
dispose(): void;
}
/**
* An event that can be listened to.
* @returns an `IDisposable` to stop listening.
*/
export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
}
}
+5 -3
View File
@@ -1025,9 +1025,11 @@ declare module 'vscode-xterm' {
charMeasure?: { height: number, width: number };
renderer: {
_renderLayers: any[];
onIntersectionChange: any;
_renderCoordinator: {
_renderer: {
_renderLayers: any[];
};
_onIntersectionChange: any;
};
}
+27 -2
View File
@@ -15,6 +15,7 @@ import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { isMacintosh } from 'vs/base/common/platform';
export interface IDialogOptions {
cancelId?: number;
@@ -30,6 +31,11 @@ export interface IDialogStyles extends IButtonStyles {
dialogBorder?: Color;
}
interface ButtonMapEntry {
label: string;
index: number;
}
export class Dialog extends Disposable {
private element: HTMLElement | undefined;
private modal: HTMLElement | undefined;
@@ -92,12 +98,13 @@ export class Dialog extends Disposable {
let focusedButton = 0;
this.buttonGroup = new ButtonGroup(this.buttonsContainer, this.buttons.length, { title: true });
const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId);
this.buttonGroup.buttons.forEach((button, index) => {
button.label = mnemonicButtonLabel(this.buttons[index], true);
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
this._register(button.onDidClick(e => {
EventHelper.stop(e);
resolve(index);
resolve(buttonMap[index].index);
}));
});
@@ -228,4 +235,22 @@ export class Dialog extends Disposable {
this.focusToReturn = undefined;
}
}
private rearrangeButtons(buttons: Array<string>, cancelId: number | undefined): ButtonMapEntry[] {
const buttonMap: ButtonMapEntry[] = [];
// Maps each button to its current label and old index so that when we move them around it's not a problem
buttons.forEach((button, index) => {
buttonMap.push({ label: button, index: index });
});
if (isMacintosh) {
if (cancelId !== undefined) {
const cancelButton = buttonMap.splice(cancelId, 1)[0];
buttonMap.reverse();
buttonMap.splice(buttonMap.length - 1, 0, cancelButton);
}
}
return buttonMap;
}
}
+2 -2
View File
@@ -17,8 +17,8 @@ export interface IListVirtualDelegate<T> {
export interface IListRenderer<T, TTemplateData> {
templateId: string;
renderTemplate(container: HTMLElement): TTemplateData;
renderElement(element: T, index: number, templateData: TTemplateData, dynamicHeightProbing?: boolean): void;
disposeElement?(element: T, index: number, templateData: TTemplateData, dynamicHeightProbing?: boolean): void;
renderElement(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
disposeElement?(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
disposeTemplate(templateData: TTemplateData): void;
}
+3 -3
View File
@@ -35,7 +35,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
return { data, disposable: { dispose: () => { } } };
}
renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, height: number | undefined): void {
if (data.disposable) {
data.disposable.dispose();
}
@@ -47,7 +47,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
const model = this.modelProvider();
if (model.isResolved(index)) {
return this.renderer.renderElement(model.get(index), index, data.data, dynamicHeightProbing);
return this.renderer.renderElement(model.get(index), index, data.data, height);
}
const cts = new CancellationTokenSource();
@@ -55,7 +55,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
data.disposable = { dispose: () => cts.cancel() };
this.renderer.renderPlaceholder(index, data.data);
promise.then(entry => this.renderer.renderElement(entry, index, data.data!, dynamicHeightProbing));
promise.then(entry => this.renderer.renderElement(entry, index, data.data!, height));
}
disposeTemplate(data: ITemplateData<TTemplateData>): void {
+4 -4
View File
@@ -586,7 +586,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
if (renderer) {
renderer.renderElement(item.element, index, item.row.templateData);
renderer.renderElement(item.element, index, item.row.templateData, item.size);
}
const uri = this.dnd.getDragURI(item.element);
@@ -647,7 +647,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const renderer = this.renderers.get(item.templateId);
if (renderer && renderer.disposeElement) {
renderer.disposeElement(item.element, index, item.row!.templateData);
renderer.disposeElement(item.element, index, item.row!.templateData, item.size);
}
this.cache.release(item.row!);
@@ -1088,10 +1088,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const renderer = this.renderers.get(item.templateId);
if (renderer) {
renderer.renderElement(item.element, index, row.templateData, true);
renderer.renderElement(item.element, index, row.templateData, undefined);
if (renderer.disposeElement) {
renderer.disposeElement(item.element, index, row.templateData, true);
renderer.disposeElement(item.element, index, row.templateData, undefined);
}
}
+4 -4
View File
@@ -979,20 +979,20 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
return this.renderers.map(r => r.renderTemplate(container));
}
renderElement(element: T, index: number, templateData: any[], dynamicHeightProbing?: boolean): void {
renderElement(element: T, index: number, templateData: any[], height: number | undefined): void {
let i = 0;
for (const renderer of this.renderers) {
renderer.renderElement(element, index, templateData[i++], dynamicHeightProbing);
renderer.renderElement(element, index, templateData[i++], height);
}
}
disposeElement(element: T, index: number, templateData: any[], dynamicHeightProbing?: boolean): void {
disposeElement(element: T, index: number, templateData: any[], height: number | undefined): void {
let i = 0;
for (const renderer of this.renderers) {
if (renderer.disposeElement) {
renderer.disposeElement(element, index, templateData[i], dynamicHeightProbing);
renderer.disposeElement(element, index, templateData[i], height);
}
i += 1;
+9
View File
@@ -105,6 +105,15 @@ export class Menu extends ActionBar {
this.menuDisposables = [];
addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
const event = new StandardKeyboardEvent(e);
// Stop tab navigation of menus
if (event.equals(KeyCode.Tab)) {
EventHelper.stop(e, true);
}
});
if (options.enableMnemonics) {
this.menuDisposables.push(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
const key = e.key.toLocaleLowerCase();
+6 -6
View File
@@ -240,8 +240,8 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
return { container, twistie, templateData };
}
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
if (!dynamicHeightProbing) {
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (typeof height === 'number') {
this.renderedNodes.set(node, templateData);
this.renderedElements.set(node.element, node);
}
@@ -250,15 +250,15 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
templateData.twistie.style.marginLeft = `${indent}px`;
this.update(node, templateData);
this.renderer.renderElement(node, index, templateData.templateData, dynamicHeightProbing);
this.renderer.renderElement(node, index, templateData.templateData, height);
}
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.templateData, dynamicHeightProbing);
this.renderer.disposeElement(node, index, templateData.templateData, height);
}
if (!dynamicHeightProbing) {
if (typeof height === 'number') {
this.renderedNodes.delete(node);
this.renderedElements.delete(node.element);
}
+4 -4
View File
@@ -98,8 +98,8 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
return { templateData };
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, dynamicHeightProbing);
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
}
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
@@ -107,9 +107,9 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, dynamicHeightProbing);
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
}
}
+7 -1
View File
@@ -50,6 +50,12 @@ export function createCancelablePromise<T>(callback: (token: CancellationToken)
};
}
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken): Promise<T | undefined>;
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue: T): Promise<T>;
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue?: T): Promise<T> {
return Promise.race([promise, new Promise<T>(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]);
}
export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const item = callback();
@@ -767,4 +773,4 @@ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries:
}
return Promise.reject(lastError);
}
}
-19
View File
@@ -362,25 +362,6 @@ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
return score ? createMatches(score) : null;
}
export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore {
const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
if (result) {
return result;
}
let matches = 0;
let score = 0;
let idx = _wordPos;
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
if (wordPos >= 0) {
score += 1;
matches += 2 ** wordPos;
idx = wordPos + 1;
}
}
return [score, matches, _wordPos];
}
//#region --- fuzzyScore ---
export function createMatches(score: undefined | FuzzyScore): IMatch[] {
+7 -1
View File
@@ -11,5 +11,11 @@
</body>
<!-- Startup via workbench.js -->
<script src="../../../../../out/vs/code/browser/workbench/workbench.js"></script>
<script>
self.CONNECTION_AUTH_TOKEN = '{{CONNECTION_AUTH_TOKEN}}';
self.USER_HOME_DIR = '{{USER_HOME_DIR}}';
</script>
<!-- Startup via workbench.js -->
<script src="./out/vs/code/browser/workbench/workbench.js"></script>
</html>
+1 -1
View File
@@ -17,7 +17,7 @@
document.head.appendChild(script);
}
loadScript('../../../../../out/vs/loader.js', function () {
loadScript('./out/vs/loader.js', function () {
// @ts-ignore
require.config({
@@ -33,7 +33,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel';
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
@@ -300,7 +300,7 @@ export class IssueReporter extends Disposable {
serviceCollection.set(IWindowsService, new WindowsService(mainProcessService));
this.environmentService = new EnvironmentService(configuration, configuration.execPath);
const logService = createSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
const logService = createBufferSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel'));
this.logService = new FollowerLogService(logLevelClient, logService);
@@ -30,7 +30,7 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen
import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows';
import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService';
import { ipcRenderer } from 'electron';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
@@ -94,7 +94,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const mainRouter = new StaticRouter(ctx => ctx === 'main');
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter));
const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
const logService = new FollowerLogService(logLevelClient, createBufferSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
disposables.push(logService);
logService.info('main', JSON.stringify(configuration));
@@ -122,7 +122,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const services = new ServiceCollection();
const environmentService = accessor.get(IEnvironmentService);
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
const telemetryLogService = new FollowerLogService(logLevelClient, createSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
const telemetryLogService = new FollowerLogService(logLevelClient, createBufferSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.');
telemetryLogService.info('===========================================================');
+1 -1
View File
@@ -288,7 +288,7 @@ function startup(args: ParsedArgs): void {
return Promise.reject(error);
})
.then(mainIpcServer => {
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath).then(logger => bufferLogService.logger = logger);
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
});
+2 -2
View File
@@ -31,7 +31,7 @@ import { mkdirp, writeFile } from 'vs/base/node/pfs';
import { getBaseLabel } from 'vs/base/common/labels';
import { IStateService } from 'vs/platform/state/common/state';
import { StateService } from 'vs/platform/state/node/stateService';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -291,7 +291,7 @@ export function main(argv: ParsedArgs): Promise<void> {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(argv, process.execPath);
const logService = createSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
const logService = createBufferSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
process.once('exit', () => logService.dispose());
logService.info('main', argv);
+3 -2
View File
@@ -10,6 +10,7 @@ import { Range } from 'vs/editor/common/core/range';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
import { EditorKeybindingCancellationTokenSource } from 'vs/editor/browser/core/keybindingCancellation';
export const enum CodeEditorStateFlag {
Value = 1,
@@ -78,12 +79,12 @@ export class EditorState {
* A cancellation token source that cancels when the editor changes as expressed
* by the provided flags
*/
export class EditorStateCancellationTokenSource extends CancellationTokenSource {
export class EditorStateCancellationTokenSource extends EditorKeybindingCancellationTokenSource {
private readonly _listener: IDisposable[] = [];
constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, parent?: CancellationToken) {
super(parent);
super(editor, parent);
if (flags & CodeEditorStateFlag.Position) {
this._listener.push(editor.onDidChangeCursorPosition(_ => this.cancel()));
@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyCode } from 'vs/base/common/keyCodes';
import { EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { LinkedList } from 'vs/base/common/linkedList';
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
const IEditorCancellationTokens = createDecorator<IEditorCancellationTokens>('IEditorCancelService');
interface IEditorCancellationTokens {
_serviceBrand: any;
add(editor: ICodeEditor, cts: CancellationTokenSource): () => void;
cancel(editor: ICodeEditor): void;
}
const ctxCancellableOperation = new RawContextKey('cancellableOperation', false);
registerSingleton(IEditorCancellationTokens, class implements IEditorCancellationTokens {
_serviceBrand: any;
private readonly _tokens = new WeakMap<ICodeEditor, { key: IContextKey<boolean>, tokens: LinkedList<CancellationTokenSource> }>();
add(editor: ICodeEditor, cts: CancellationTokenSource): () => void {
let data = this._tokens.get(editor);
if (!data) {
data = editor.invokeWithinContext(accessor => {
const key = ctxCancellableOperation.bindTo(accessor.get(IContextKeyService));
const tokens = new LinkedList<CancellationTokenSource>();
return { key, tokens };
});
this._tokens.set(editor, data);
}
let removeFn: Function | undefined;
data.key.set(true);
removeFn = data.tokens.push(cts);
return () => {
// remove w/o cancellation
if (removeFn) {
removeFn();
data!.key.set(!data!.tokens.isEmpty());
removeFn = undefined;
}
};
}
cancel(editor: ICodeEditor): void {
const data = this._tokens.get(editor);
if (!data) {
return;
}
// remove with cancellation
const cts = data.tokens.pop();
if (cts) {
cts.cancel();
data.key.set(!data.tokens.isEmpty());
}
}
}, true);
export class EditorKeybindingCancellationTokenSource extends CancellationTokenSource {
private readonly _unregister: Function;
constructor(readonly editor: ICodeEditor, parent?: CancellationToken) {
super(parent);
this._unregister = editor.invokeWithinContext(accessor => accessor.get(IEditorCancellationTokens).add(editor, this));
}
dispose(): void {
this._unregister();
super.dispose();
}
}
registerEditorCommand(new class extends EditorCommand {
constructor() {
super({
id: 'editor.cancelOperation',
kbOpts: {
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.Escape
},
precondition: ctxCancellableOperation
});
}
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
accessor.get(IEditorCancellationTokens).cancel(editor);
}
});
@@ -323,9 +323,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
}
if (this._renderSideBySide) {
this._setStrategy(new DiffEdtorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
} else {
this._setStrategy(new DiffEdtorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
}
this._register(themeService.onThemeChange(t => {
@@ -597,9 +597,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
// renderSideBySide
if (renderSideBySideChanged) {
if (this._renderSideBySide) {
this._setStrategy(new DiffEdtorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
} else {
this._setStrategy(new DiffEdtorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
}
// Update class name
this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide);
@@ -1547,7 +1547,7 @@ const DECORATIONS = {
};
class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle, IVerticalSashLayoutProvider {
class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle, IVerticalSashLayoutProvider {
static MINIMUM_EDITOR_WIDTH = 100;
@@ -1592,13 +1592,13 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd
sashPosition = this._disableSash ? midPoint : sashPosition || midPoint;
if (contentWidth > DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH * 2) {
if (sashPosition < DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
sashPosition = DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
if (contentWidth > DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH * 2) {
if (sashPosition < DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
sashPosition = DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
}
if (sashPosition > contentWidth - DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
sashPosition = contentWidth - DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
if (sashPosition > contentWidth - DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
sashPosition = contentWidth - DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
}
} else {
sashPosition = midPoint;
@@ -1807,7 +1807,7 @@ class SideBySideViewZonesComputer extends ViewZonesComputer {
}
}
class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle {
class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle {
private decorationsLeft: number;
@@ -248,7 +248,7 @@ class EditorModelManager extends Disposable {
super.dispose();
}
public esureSyncedResources(resources: URI[]): void {
public ensureSyncedResources(resources: URI[]): void {
for (const resource of resources) {
let resourceStr = resource.toString();
@@ -387,7 +387,7 @@ export class EditorWorkerClient extends Disposable {
protected _withSyncedResources(resources: URI[]): Promise<EditorSimpleWorkerImpl> {
return this._getProxy().then((proxy) => {
this._getOrCreateModelManager(proxy).esureSyncedResources(resources);
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources);
return proxy;
});
}
+6 -8
View File
@@ -8,7 +8,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { CodeEditorStateFlag, EditorState, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
import { CodeEditorStateFlag, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerLanguageCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position';
@@ -143,27 +143,25 @@ export async function formatDocumentRangeWithProvider(
const workerService = accessor.get(IEditorWorkerService);
let model: ITextModel;
let validate: () => boolean;
let cts: CancellationTokenSource;
if (isCodeEditor(editorOrModel)) {
model = editorOrModel.getModel();
const state = new EditorState(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
validate = () => state.validate(editorOrModel);
cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, token);
} else {
model = editorOrModel;
const versionNow = editorOrModel.getVersionId();
validate = () => versionNow === editorOrModel.getVersionId();
cts = new TextModelCancellationTokenSource(editorOrModel, token);
}
const rawEdits = await provider.provideDocumentRangeFormattingEdits(
model,
range,
model.getFormattingOptions(),
token
cts.token
);
const edits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits);
if (!validate()) {
if (cts.token.isCancellationRequested) {
return true;
}
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { alert } from 'vs/base/browser/ui/aria/aria';
import { createCancelablePromise } from 'vs/base/common/async';
import { createCancelablePromise, raceCancellation } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
@@ -64,9 +64,9 @@ export class DefinitionAction extends EditorAction {
const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
const definitionPromise = this._getTargetLocationForPosition(model, pos, cts.token).then(async references => {
const definitionPromise = raceCancellation(this._getTargetLocationForPosition(model, pos, cts.token), cts.token).then(async references => {
if (cts.token.isCancellationRequested || model.isDisposed() || editor.getModel() !== model) {
if (!references || model.isDisposed()) {
// new model, no more model
return;
}
@@ -3,13 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
import { fuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
import { isDisposable } from 'vs/base/common/lifecycle';
import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes';
import { CompletionItem } from './suggest';
import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
import { CharCode } from 'vs/base/common/charCode';
import { compareIgnoreCase } from 'vs/base/common/strings';
type StrictCompletionItem = Required<CompletionItem>;
@@ -215,8 +216,15 @@ export class CompletionModel {
if (!match) {
continue; // NO match
}
item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0);
item.score[0] = match[0]; // use score from filterText
if (compareIgnoreCase(item.completion.filterText, item.completion.label) === 0) {
// filterText and label are actually the same -> use good highlights
item.score = match;
} else {
// re-run the scorer on the label in the hope of a result BUT use the rank
// of the filterText-match
item.score = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false) || FuzzyScore.Default;
item.score[0] = match[0]; // use score from filterText
}
} else {
// by default match `word` against the `label`
@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISelectedSuggestion, SuggestWidget } from './suggestWidget';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
export class CommitCharacterController {
private _disposables: IDisposable[] = [];
private _active?: {
readonly acceptCharacters: CharacterSet;
readonly item: ISelectedSuggestion;
};
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
this._disposables.push(widget.onDidFocus(this._onItem, this));
this._disposables.push(widget.onDidHide(this.reset, this));
this._disposables.push(editor.onWillType(text => {
if (this._active) {
const ch = text.charCodeAt(text.length - 1);
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
accept(this._active.item);
}
}
}));
}
private _onItem(selected: ISelectedSuggestion | undefined): void {
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
this.reset();
return;
}
const acceptCharacters = new CharacterSet();
for (const ch of selected.item.completion.commitCharacters) {
if (ch.length > 0) {
acceptCharacters.add(ch.charCodeAt(0));
}
}
this._active = { acceptCharacters, item: selected };
}
reset(): void {
this._active = undefined;
}
dispose() {
dispose(this._disposables);
}
}
@@ -31,57 +31,8 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
import { Event } from 'vs/base/common/event';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IdleValue } from 'vs/base/common/async';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
import { isObject } from 'vs/base/common/types';
class AcceptOnCharacterOracle {
private _disposables: IDisposable[] = [];
private _active?: {
readonly acceptCharacters: CharacterSet;
readonly item: ISelectedSuggestion;
};
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
this._disposables.push(widget.onDidFocus(this._onItem, this));
this._disposables.push(widget.onDidHide(this.reset, this));
this._disposables.push(editor.onWillType(text => {
if (this._active) {
const ch = text.charCodeAt(text.length - 1);
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
accept(this._active.item);
}
}
}));
}
private _onItem(selected: ISelectedSuggestion | undefined): void {
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
this.reset();
return;
}
const acceptCharacters = new CharacterSet();
for (const ch of selected.item.completion.commitCharacters) {
if (ch.length > 0) {
acceptCharacters.add(ch.charCodeAt(0));
}
}
this._active = { acceptCharacters, item: selected };
}
reset(): void {
this._active = undefined;
}
dispose() {
dispose(this._disposables);
}
}
import { CommitCharacterController } from './suggestCommitCharacters';
export class SuggestController implements IEditorContribution {
@@ -113,15 +64,15 @@ export class SuggestController implements IEditorContribution {
const widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
this._toDispose.push(widget);
this._toDispose.push(widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this));
this._toDispose.push(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this));
// Wire up logic to accept a suggestion on certain characters
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, widget, item => this._onDidSelectItem(item, false, true));
const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, false, true));
this._toDispose.push(
autoAcceptOracle,
commitCharacterController,
this._model.onDidSuggest(e => {
if (e.completionModel.items.length === 0) {
autoAcceptOracle.reset();
commitCharacterController.reset();
}
})
);
@@ -210,7 +161,7 @@ export class SuggestController implements IEditorContribution {
}
}
protected _onDidSelectItem(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
protected _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
if (!event || !event.item) {
this._alternatives.getValue().reset();
this._model.cancel();
@@ -282,7 +233,7 @@ export class SuggestController implements IEditorContribution {
if (modelVersionNow !== model.getAlternativeVersionId()) {
model.undo();
}
this._onDidSelectItem(next, false, false);
this._insertSuggestion(next, false, false);
break;
}
});
@@ -364,7 +315,7 @@ export class SuggestController implements IEditorContribution {
return;
}
this._editor.pushUndoStop();
this._onDidSelectItem({ index, item, model: completionModel }, true, false);
this._insertSuggestion({ index, item, model: completionModel }, true, false);
}, undefined, listener);
});
@@ -375,10 +326,8 @@ export class SuggestController implements IEditorContribution {
}
acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void {
if (this._widget) {
const item = this._widget.getValue().getFocusedItem();
this._onDidSelectItem(item, !!keepAlternativeSuggestions, true);
}
const item = this._widget.getValue().getFocusedItem();
this._insertSuggestion(item, !!keepAlternativeSuggestions, true);
}
acceptNextSuggestion() {
@@ -390,58 +339,40 @@ export class SuggestController implements IEditorContribution {
}
cancelSuggestWidget(): void {
if (this._widget) {
this._model.cancel();
this._widget.getValue().hideWidget();
}
this._model.cancel();
this._widget.getValue().hideWidget();
}
selectNextSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectNext();
}
this._widget.getValue().selectNext();
}
selectNextPageSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectNextPage();
}
this._widget.getValue().selectNextPage();
}
selectLastSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectLast();
}
this._widget.getValue().selectLast();
}
selectPrevSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectPrevious();
}
this._widget.getValue().selectPrevious();
}
selectPrevPageSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectPreviousPage();
}
this._widget.getValue().selectPreviousPage();
}
selectFirstSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectFirst();
}
this._widget.getValue().selectFirst();
}
toggleSuggestionDetails(): void {
if (this._widget) {
this._widget.getValue().toggleDetails();
}
this._widget.getValue().toggleDetails();
}
toggleSuggestionFocus(): void {
if (this._widget) {
this._widget.getValue().toggleDetailsFocus();
}
this._widget.getValue().toggleDetailsFocus();
}
}
+11 -12
View File
@@ -95,7 +95,6 @@ export class SuggestModel implements IDisposable {
private _quickSuggestDelay: number;
private _triggerCharacterListener: IDisposable;
private readonly _triggerQuickSuggest = new TimeoutTimer();
private readonly _triggerRefilter = new TimeoutTimer();
private _state: State = State.Idle;
private _requestToken?: CancellationTokenSource;
@@ -161,7 +160,7 @@ export class SuggestModel implements IDisposable {
}
dispose(): void {
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest, this._triggerRefilter]);
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest]);
this._toDispose = dispose(this._toDispose);
dispose(this._completionModel);
this.cancel();
@@ -221,7 +220,6 @@ export class SuggestModel implements IDisposable {
cancel(retrigger: boolean = false): void {
if (this._state !== State.Idle) {
this._triggerRefilter.cancel();
this._triggerQuickSuggest.cancel();
if (this._requestToken) {
this._requestToken.cancel();
@@ -328,14 +326,15 @@ export class SuggestModel implements IDisposable {
}
private _refilterCompletionItems(): void {
if (this._state === State.Idle) {
return;
}
if (!this._editor.hasModel()) {
return;
}
// refine active suggestion
this._triggerRefilter.cancelAndSet(() => {
// Re-filter suggestions. This MUST run async because filtering/scoring
// uses the model content AND the cursor position. The latter is NOT
// updated when the document has changed (the event which drives this method)
// and therefore a little pause (next mirco task) is needed. See:
// https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context#25933985
Promise.resolve().then(() => {
if (this._state === State.Idle) {
return;
}
if (!this._editor.hasModel()) {
return;
}
@@ -343,7 +342,7 @@ export class SuggestModel implements IDisposable {
const position = this._editor.getPosition();
const ctx = new LineContext(model, position, this._state === State.Auto, false);
this._onNewContext(ctx);
}, 0);
});
}
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: Set<CompletionItemProvider>, existingItems?: CompletionItem[]): void {
@@ -30,7 +30,6 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CompletionItemKind, completionKindToCssClass } from 'vs/editor/common/modes';
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
@@ -545,10 +544,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
return;
}
item.resolve(CancellationToken.None).then(() => {
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
this.editor.focus();
});
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
this.editor.focus();
}
private _getSuggestionAriaAlertLabel(item: CompletionItem): string {
@@ -671,8 +671,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return withOracle(async (sugget, editor) => {
class TestCtrl extends SuggestController {
_onDidSelectItem(item: ISelectedSuggestion) {
super._onDidSelectItem(item, false, true);
_insertSuggestion(item: ISelectedSuggestion) {
super._insertSuggestion(item, false, true);
}
}
const ctrl = <TestCtrl>editor.registerAndInstantiateContribution(TestCtrl);
@@ -687,7 +687,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
const [first] = event.completionModel.items;
assert.equal(first.completion.label, 'bar');
ctrl._onDidSelectItem({ item: first, index: 0, model: event.completionModel });
ctrl._insertSuggestion({ item: first, index: 0, model: event.completionModel });
});
assert.equal(
@@ -162,7 +162,6 @@ export interface IExtensionGalleryService {
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]>;
loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]>;
getExtensionsReport(): Promise<IReportedExtension[]>;
getCompatibleExtension(extension: IGalleryExtension): Promise<IGalleryExtension | null>;
getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise<IGalleryExtension | null>;
@@ -5,7 +5,6 @@
import { tmpdir } from 'os';
import * as path from 'vs/base/common/path';
import { distinct } from 'vs/base/common/arrays';
import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -750,10 +749,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return Promise.resolve('');
}
loadAllDependencies(extensions: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
return this.getDependenciesRecursively(extensions.map(e => e.id), [], token);
}
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
let query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.ExcludeNonValidated)
@@ -782,58 +777,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private loadDependencies(extensionNames: string[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!extensionNames || extensionNames.length === 0) {
return Promise.resolve([]);
}
let query = new Query()
.withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, extensionNames.length)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished))
.withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionName, ...extensionNames);
return this.queryGallery(query, token).then(result => {
const dependencies: IGalleryExtension[] = [];
const ids: string[] = [];
for (let index = 0; index < result.galleryExtensions.length; index++) {
const rawExtension = result.galleryExtensions[index];
if (ids.indexOf(rawExtension.extensionId) === -1) {
dependencies.push(toExtension(rawExtension, rawExtension.versions[0], index, query, 'dependencies'));
ids.push(rawExtension.extensionId);
}
}
return dependencies;
});
}
private getDependenciesRecursively(toGet: string[], result: IGalleryExtension[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!toGet || !toGet.length) {
return Promise.resolve(result);
}
toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet;
if (!toGet.length) {
return Promise.resolve(result);
}
return this.loadDependencies(toGet, token)
.then(loadedDependencies => {
const dependenciesSet = new Set<string>();
for (const dep of loadedDependencies) {
if (dep.properties.dependencies) {
dep.properties.dependencies.forEach(d => dependenciesSet.add(d));
}
}
result = distinct(result.concat(loadedDependencies), d => d.identifier.uuid);
const dependencies: string[] = [];
dependenciesSet.forEach(d => !ExtensionGalleryService.hasExtensionByName(result, d) && dependencies.push(d));
return this.getDependenciesRecursively(dependencies, result, token);
});
}
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
return this.commonHeadersPromise.then(commonHeaders => {
const baseOptions = { type: 'GET' };
@@ -952,15 +895,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private static hasExtensionByName(extensions: IGalleryExtension[], name: string): boolean {
for (const extension of extensions) {
if (`${extension.publisher}.${extension.name}` === name) {
return true;
}
}
return false;
}
getExtensionsReport(): Promise<IReportedExtension[]> {
if (!this.isEnabled()) {
return Promise.reject(new Error('No extension gallery service configured.'));
@@ -16,18 +16,24 @@ export function isUIExtension(manifest: IExtensionManifest, uiContributions: str
case 'ui': return true;
case 'workspace': return false;
default: {
// Tagged as UI extension in product
if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return true;
}
// Not an UI extension if it has main
if (manifest.main) {
return false;
}
// Not an UI extension if it has dependencies or an extension pack
if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) {
return false;
}
if (manifest.contributes) {
// Not an UI extension if it has no ui contributions
if (!uiContributions.length || Object.keys(manifest.contributes).some(contribution => uiContributions.indexOf(contribution) === -1)) {
return false;
}
}
// Default is UI Extension
return true;
}
}
+9 -2
View File
@@ -6,14 +6,15 @@
import * as path from 'vs/base/common/path';
import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log';
import * as spdlog from 'spdlog';
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
export function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService {
export async function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): Promise<ILogService> {
// Do not crash if spdlog cannot be loaded
try {
const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog');
_spdlog.setAsyncMode(8192, 500);
const logfilePath = path.join(logsFolder, `${processName}.log`);
const logger = new _spdlog.RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
const logger = await _spdlog.createRotatingLoggerAsync(processName, logfilePath, 1024 * 1024 * 5, 6);
logger.setLevel(0);
return new SpdLogService(logger, logLevel);
@@ -28,6 +29,12 @@ export function createRotatingLogger(name: string, filename: string, filesize: n
return _spdlog.createRotatingLogger(name, filename, filesize, filecount);
}
export function createBufferSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService {
const bufferLogService = new BufferLogService();
createSpdLogService(processName, logLevel, logsFolder).then(logger => bufferLogService.logger = logger);
return bufferLogService;
}
class SpdLogService extends AbstractLogService implements ILogService {
_serviceBrand: any;
+1
View File
@@ -67,6 +67,7 @@ export interface IProductConfiguration {
keyboardShortcutsUrlWin: string;
introductoryVideosUrl: string;
tipsAndTricksUrl: string;
newsletterSignupUrl: string;
twitterUrl: string;
requestFeatureUrl: string;
reportIssueUrl: string;
@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWebSocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection';
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
import { VSBuffer } from 'vs/base/common/buffer';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
class BrowserSocket implements ISocket {
public readonly socket: WebSocket;
constructor(socket: WebSocket) {
this.socket = socket;
}
public dispose(): void {
this.socket.close();
}
public onData(_listener: (e: VSBuffer) => void): IDisposable {
const fileReader = new FileReader();
const queue: Blob[] = [];
let isReading = false;
fileReader.onload = function (event) {
isReading = false;
const buff = <ArrayBuffer>(<any>event.target).result;
try {
_listener(VSBuffer.wrap(new Uint8Array(buff)));
} catch (err) {
onUnexpectedError(err);
}
if (queue.length > 0) {
enqueue(queue.shift()!);
}
};
const enqueue = (blob: Blob) => {
if (isReading) {
queue.push(blob);
return;
}
isReading = true;
fileReader.readAsArrayBuffer(blob);
};
const listener = (e: MessageEvent) => {
enqueue(<Blob>e.data);
};
this.socket.addEventListener('message', listener);
return {
dispose: () => this.socket.removeEventListener('message', listener)
};
}
public onClose(listener: () => void): IDisposable {
this.socket.addEventListener('close', listener);
return {
dispose: () => this.socket.removeEventListener('close', listener)
};
}
public onEnd(listener: () => void): IDisposable {
return Disposable.None;
}
public write(buffer: VSBuffer): void {
this.socket.send(buffer.buffer);
}
public end(): void {
this.socket.close();
}
}
export const browserWebSocketFactory = new class implements IWebSocketFactory {
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
const errorListener = (err: any) => callback(err, undefined);
const socket = new WebSocket(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
socket.onopen = function (event) {
socket.removeEventListener('error', errorListener);
callback(undefined, new BrowserSocket(socket));
};
socket.addEventListener('error', errorListener);
}
};
@@ -3,11 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Client, PersistentProtocol, ISocket } from 'vs/base/parts/ipc/common/ipc.net';
import { Client, PersistentProtocol, ISocket, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
import { generateUuid } from 'vs/base/common/uuid';
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { Disposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
import * as platform from 'vs/base/common/platform';
import { Emitter } from 'vs/base/common/event';
import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isPromiseCanceledError } from 'vs/base/common/errors';
export const enum ConnectionType {
Management = 1,
@@ -15,6 +19,37 @@ export const enum ConnectionType {
Tunnel = 3,
}
export interface AuthRequest {
type: 'auth';
auth: string;
}
export interface SignRequest {
type: 'sign';
data: string;
}
export interface ConnectionTypeRequest {
type: 'connectionType';
commit?: string;
signedData?: string;
desiredConnectionType?: ConnectionType;
args?: any;
isBuilt: boolean;
}
export interface ErrorMessage {
type: 'error';
reason: string;
}
export interface OKMessage {
type: 'ok';
}
export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage;
interface ISimpleConnectionOptions {
isBuilt: boolean;
commit: string | undefined;
@@ -34,7 +69,84 @@ export interface IWebSocketFactory {
}
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<PersistentProtocol> {
throw new Error(`Not implemented`);
const protocol = await new Promise<PersistentProtocol>((c, e) => {
options.webSocketFactory.connect(
options.host,
options.port,
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
(err: any, socket: ISocket) => {
if (err) {
e(err);
return;
}
if (options.reconnectionProtocol) {
options.reconnectionProtocol.beginAcceptReconnection(socket, null);
c(options.reconnectionProtocol);
} else {
c(new PersistentProtocol(socket, null));
}
}
);
});
return new Promise<PersistentProtocol>((c, e) => {
const messageRegistration = protocol.onControlMessage(raw => {
const msg = <HandshakeMessage>JSON.parse(raw.toString());
// Stop listening for further events
messageRegistration.dispose();
const error = getErrorFromMessage(msg);
if (error) {
return e(error);
}
if (msg.type === 'sign') {
let signed = msg.data;
if (platform.isNative) {
try {
const vsda = <any>require.__$__nodeRequire('vsda');
const signer = new vsda.signer();
if (signer) {
signed = signer.sign(msg.data);
}
} catch (e) {
console.error('signer.sign: ' + e);
}
} else {
signed = (<any>self).CONNECTION_AUTH_TOKEN;
}
const connTypeRequest: ConnectionTypeRequest = {
type: 'connectionType',
commit: options.commit,
signedData: signed,
desiredConnectionType: connectionType,
isBuilt: options.isBuilt
};
if (args) {
connTypeRequest.args = args;
}
protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest)));
c(protocol);
} else {
e(new Error('handshake error'));
}
});
setTimeout(() => {
e(new Error('handshake timeout'));
}, 2000);
// TODO@vs-remote: use real nonce here
const authRequest: AuthRequest = {
type: 'auth',
auth: '00000000000000000000'
};
protocol.sendControl(VSBuffer.fromString(JSON.stringify(authRequest)));
});
}
interface IManagementConnectionResult {
@@ -148,6 +260,12 @@ export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunn
return protocol;
}
function sleep(seconds: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
setTimeout(resolve, seconds * 1000);
});
}
export const enum PersistenConnectionEventType {
ConnectionLost,
ReconnectionWait,
@@ -184,11 +302,99 @@ abstract class PersistentConnection extends Disposable {
public readonly reconnectionToken: string;
public readonly protocol: PersistentProtocol;
private _isReconnecting: boolean;
private _permanentFailure: boolean;
constructor(options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) {
super();
this._options = options;
this.reconnectionToken = reconnectionToken;
this.protocol = protocol;
this._isReconnecting = false;
this._permanentFailure = false;
this._register(protocol.onSocketClose(() => this._beginReconnecting()));
this._register(protocol.onSocketTimeout(() => this._beginReconnecting()));
}
private async _beginReconnecting(): Promise<void> {
// Only have one reconnection loop active at a time.
if (this._isReconnecting) {
return;
}
try {
this._isReconnecting = true;
await this._runReconnectingLoop();
} finally {
this._isReconnecting = false;
}
}
private async _runReconnectingLoop(): Promise<void> {
if (this._permanentFailure) {
// no more attempts!
return;
}
this._onDidStateChange.fire(new ConnectionLostEvent());
const TIMES = [5, 5, 10, 10, 10, 10, 10, 30];
const disconnectStartTime = Date.now();
let attempt = -1;
do {
attempt++;
const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]);
try {
this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime));
await sleep(waitTime);
// connection was lost, let's try to re-establish it
this._onDidStateChange.fire(new ReconnectionRunningEvent());
const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol);
await connectWithTimeLimit(this._reconnect(simpleOptions), 30 * 1000 /*30s*/);
this._onDidStateChange.fire(new ConnectionGainEvent());
break;
} catch (err) {
if (err.code === 'VSCODE_CONNECTION_ERROR') {
console.error(`A permanent connection error occurred`);
console.error(err);
this._permanentFailure = true;
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
this.protocol.acceptDisconnect();
break;
}
if (Date.now() - disconnectStartTime > ProtocolConstants.ReconnectionGraceTime) {
console.error(`Giving up after reconnection grace time has expired!`);
this._permanentFailure = true;
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
this.protocol.acceptDisconnect();
break;
}
if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) {
console.warn(`A temporarily not available error occured while trying to reconnect:`);
console.warn(err);
// try again!
continue;
}
if ((err.code === 'ETIMEDOUT' || err.code === 'ENETUNREACH' || err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') && err.syscall === 'connect') {
console.warn(`A connect error occured while trying to reconnect:`);
console.warn(err);
// try again!
continue;
}
if (isPromiseCanceledError(err)) {
console.warn(`A cancel error occured while trying to reconnect:`);
console.warn(err);
// try again!
continue;
}
console.error(`An error occured while trying to reconnect:`);
console.error(err);
this._permanentFailure = true;
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
this.protocol.acceptDisconnect();
break;
}
} while (!this._permanentFailure);
}
protected abstract _reconnect(options: ISimpleConnectionOptions): Promise<void>;
@@ -227,6 +433,24 @@ export class ExtensionHostPersistentConnection extends PersistentConnection {
}
}
function connectWithTimeLimit(p: Promise<void>, timeLimit: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
let timeout = setTimeout(() => {
const err: any = new Error('Time limit reached');
err.code = 'ETIMEDOUT';
err.syscall = 'connect';
reject(err);
}, timeLimit);
p.then(() => {
clearTimeout(timeout);
resolve();
}, (err) => {
clearTimeout(timeout);
reject(err);
});
});
}
function getErrorFromMessage(msg: any): Error | null {
if (msg && msg.type === 'error') {
const error = new Error(`Connection error: ${msg.reason}`);
@@ -3,11 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import BaseErrorTelemetry from '../common/errorTelemetry';
export default class ErrorTelemetry extends BaseErrorTelemetry {
protected installErrorListeners(): void {
setUnexpectedErrorHandler(err => console.error(err));
// Print a console message when rejection isn't handled within N seconds. For details:
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
@@ -5,7 +5,7 @@
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { URI as uri } from 'vs/base/common/uri';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDebugAdapterTrackerFactory } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug';
import {
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
@@ -26,7 +26,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
private _debugAdaptersHandleCounter = 1;
private readonly _debugConfigurationProviders: Map<number, IDebugConfigurationProvider>;
private readonly _debugAdapterDescriptorFactories: Map<number, IDebugAdapterDescriptorFactory>;
private readonly _debugAdapterTrackerFactories: Map<number, IDebugAdapterTrackerFactory>;
private readonly _sessions: Set<DebugSessionUUID>;
constructor(
@@ -53,7 +52,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
this._debugAdapters = new Map();
this._debugConfigurationProviders = new Map();
this._debugAdapterDescriptorFactories = new Map();
this._debugAdapterTrackerFactories = new Map();
this._sessions = new Set();
}
@@ -207,24 +205,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
}
}
public $registerDebugAdapterTrackerFactory(debugType: string, handle: number) {
const factory = <IDebugAdapterTrackerFactory>{
type: debugType,
};
this._debugAdapterTrackerFactories.set(handle, factory);
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterTrackerFactory(factory));
return Promise.resolve(undefined);
}
public $unregisterDebugAdapterTrackerFactory(handle: number) {
const factory = this._debugAdapterTrackerFactories.get(handle);
if (factory) {
this._debugAdapterTrackerFactories.delete(handle);
this.debugService.getConfigurationManager().unregisterDebugAdapterTrackerFactory(factory);
}
}
private getSession(sessionId: DebugSessionUUID | undefined): IDebugSession | undefined {
if (sessionId) {
return this.debugService.getModel().getSession(sessionId, true);
@@ -686,10 +686,8 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void;
$registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasProvideDaMethod: boolean, handle: number): Promise<void>;
$registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise<void>;
$registerDebugAdapterTrackerFactory(type: string, handle: number): Promise<any>;
$unregisterDebugConfigurationProvider(handle: number): void;
$unregisterDebugAdapterDescriptorFactory(handle: number): void;
$unregisterDebugAdapterTrackerFactory(handle: number): void;
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, parentSessionID: string | undefined): Promise<boolean>;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
$appendDebugConsole(value: string): void;
@@ -21,6 +21,7 @@ namespace schema {
export interface IUserFriendlyMenuItem {
command: string;
alt?: string;
precondition?: string;
when?: string;
group?: string;
}
@@ -79,6 +80,10 @@ namespace schema {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt'));
return false;
}
if (item.precondition && typeof item.precondition !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition'));
return false;
}
if (item.when && typeof item.when !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
@@ -103,6 +108,10 @@ namespace schema {
description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'),
type: 'string'
},
precondition: {
description: localize('vscode.extension.contributes.menuItem.precondition', 'Condition which must be true to enable this item'),
type: 'string'
},
when: {
description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'),
type: 'string'
@@ -114,7 +123,7 @@ namespace schema {
}
};
export const menusContribtion: IJSONSchema = {
export const menusContribution: IJSONSchema = {
description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"),
type: 'object',
properties: {
@@ -354,7 +363,7 @@ let _menuRegistrations: IDisposable[] = [];
ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({
extensionPoint: 'menus',
jsonSchema: schema.menusContribtion
jsonSchema: schema.menusContribution
}).setHandler(extensions => {
// remove all previous menu registrations
@@ -406,6 +415,14 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM
}
}
if (item.precondition) {
command.precondition = ContextKeyExpr.deserialize(item.precondition);
}
if (alt && item.precondition) {
alt.precondition = command.precondition;
}
const registration = MenuRegistry.appendMenuItem(menu, {
command,
alt,
@@ -210,11 +210,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
}
}
public $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise<boolean> {
public async $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise<boolean> {
if (typeof handle === 'number') {
this.getWebviewElement(handle).sendMessage(message);
return Promise.resolve(true);
return true;
} else {
const webview = this.getWebview(handle);
const editors = this._editorService.visibleControls
@@ -222,11 +221,17 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
.map(e => e as WebviewEditor)
.filter(e => e.input!.matches(webview));
for (const editor of editors) {
editor.sendMessage(message);
if (editors.length > 0) {
editors[0].sendMessage(message);
return true;
}
return Promise.resolve(editors.length > 0);
if (webview.webview) {
webview.webview.sendMessage(message);
return true;
}
return false;
}
}
@@ -307,11 +307,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
const handle = this._trackerFactoryHandleCounter++;
this._trackerFactories.push({ type, handle, factory });
this._debugServiceProxy.$registerDebugAdapterTrackerFactory(type, handle);
return new Disposable(() => {
this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove
this._debugServiceProxy.$unregisterDebugAdapterTrackerFactory(handle);
});
}
@@ -333,11 +333,6 @@ export class SimpleExtensionGalleryService implements IExtensionGalleryService {
return Promise.resolve(undefined);
}
loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
// @ts-ignore
return Promise.resolve(undefined);
}
getExtensionsReport(): Promise<IReportedExtension[]> {
// @ts-ignore
return Promise.resolve(undefined);
+3 -13
View File
@@ -24,7 +24,7 @@ import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExte
import { Registry } from 'vs/platform/registry/common/platform';
import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal';
export const VIEWLET_ID = 'workbench.view.debug';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
@@ -111,7 +111,7 @@ export interface IExpression extends IReplElement, IExpressionContainer {
}
export interface IDebugger {
createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter>;
createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter>;
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined>;
getCustomTelemetryService(): Promise<TelemetryService | undefined>;
}
@@ -573,13 +573,7 @@ export interface ITerminalSettings {
osxExec: string,
linuxExec: string
};
integrated: {
shell: {
osx: string,
windows: string,
linux: string
}
};
integrated: ITerminalConfiguration;
}
export interface IConfigurationManager {
@@ -609,7 +603,6 @@ export interface IConfigurationManager {
activateDebuggers(activationEvent: string, debugType?: string): Promise<void>;
needsToRunInExtHost(debugType: string): boolean;
hasDebugConfigurationProvider(debugType: string): boolean;
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable;
@@ -618,9 +611,6 @@ export interface IConfigurationManager {
registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable;
unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void;
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable;
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void;
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise<any>;
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined>;
@@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/node/debugger';
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -52,7 +52,6 @@ export class ConfigurationManager implements IConfigurationManager {
private _onDidSelectConfigurationName = new Emitter<void>();
private configProviders: IDebugConfigurationProvider[];
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
private adapterTrackerFactories: IDebugAdapterTrackerFactory[];
private debugAdapterFactories: Map<string, IDebugAdapterFactory>;
private terminalLauncher: ITerminalLauncher;
private debugConfigurationTypeContext: IContextKey<string>;
@@ -72,7 +71,6 @@ export class ConfigurationManager implements IConfigurationManager {
) {
this.configProviders = [];
this.adapterDescriptorFactories = [];
this.adapterTrackerFactories = [];
this.debuggers = [];
this.toDispose = [];
this.registerListeners(lifecycleService);
@@ -164,24 +162,6 @@ export class ConfigurationManager implements IConfigurationManager {
return Promise.resolve(undefined);
}
// debug adapter trackers
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable {
this.adapterTrackerFactories.push(debugAdapterTrackerFactory);
return {
dispose: () => {
this.unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory);
}
};
}
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void {
const ix = this.adapterTrackerFactories.indexOf(debugAdapterTrackerFactory);
if (ix >= 0) {
this.adapterTrackerFactories.splice(ix, 1);
}
}
// debug configurations
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
@@ -206,13 +186,6 @@ export class ConfigurationManager implements IConfigurationManager {
return providers.length > 0;
}
needsToRunInExtHost(debugType: string): boolean {
// if the given debugType matches any registered tracker factory we need to run the DA in the EH
const providers = this.adapterTrackerFactories.filter(p => p.type === debugType || p.type === '*');
return providers.length > 0;
}
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise<IConfig | null | undefined> {
return this.activateDebuggers('onDebugResolve', type).then(() => {
// pipe the config through the promises sequentially. Append at the end the '*' types
@@ -25,7 +25,6 @@ import { generateUuid } from 'vs/base/common/uuid';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { Range } from 'vs/editor/common/core/range';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -62,7 +61,6 @@ export class DebugSession implements IDebugSession {
private _parentSession: IDebugSession | undefined,
@IDebugService private readonly debugService: IDebugService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IOutputService private readonly outputService: IOutputService,
@IWindowService private readonly windowService: IWindowService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IViewletService private readonly viewletService: IViewletService,
@@ -167,7 +165,7 @@ export class DebugSession implements IDebugSession {
return dbgr.getCustomTelemetryService().then(customTelemetryService => {
return dbgr.createDebugAdapter(this, this.outputService).then(debugAdapter => {
return dbgr.createDebugAdapter(this).then(debugAdapter => {
this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.environmentService);
@@ -11,11 +11,8 @@ import { isObject } from 'vs/base/common/types';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
@@ -38,7 +35,6 @@ export class Debugger implements IDebugger {
constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService,
@ICommandService private readonly commandService: ICommandService,
@IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
@@ -97,97 +93,25 @@ export class Debugger implements IDebugger {
}
}
public createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter> {
public createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter> {
return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => {
if (this.inExtHost()) {
const da = this.configurationManager.createDebugAdapter(session);
if (da) {
return Promise.resolve(da);
}
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
} else {
return this.getAdapterDescriptor(session).then(adapterDescriptor => {
switch (adapterDescriptor.type) {
case 'executable':
return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService);
case 'server':
return new SocketDebugAdapter(adapterDescriptor);
case 'implementation':
// TODO@AW: this.inExtHost() should now return true
return Promise.resolve(this.configurationManager.createDebugAdapter(session));
default:
throw new Error('unknown descriptor type');
}
}).catch(err => {
if (err && err.message) {
throw new Error(nls.localize('cannot.create.da.with.err', "Cannot create debug adapter ({0}).", err.message));
} else {
throw new Error(nls.localize('cannot.create.da', "Cannot create debug adapter."));
}
});
const da = this.configurationManager.createDebugAdapter(session);
if (da) {
return Promise.resolve(da);
}
});
}
private getAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor> {
// a "debugServer" attribute in the launch config takes precedence
if (typeof session.configuration.debugServer === 'number') {
return Promise.resolve(<IDebugAdapterServer>{
type: 'server',
port: session.configuration.debugServer
});
}
// try the new "createDebugAdapterDescriptor" and the deprecated "provideDebugAdapter" API
return this.configurationManager.getDebugAdapterDescriptor(session).then(adapter => {
if (adapter) {
return adapter;
}
// try deprecated command based extension API "adapterExecutableCommand" to determine the executable
if (this.debuggerContribution.adapterExecutableCommand) {
console.info('debugAdapterExecutable attribute in package.json is deprecated and support for it will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.');
const rootFolder = session.root ? session.root.uri.toString() : undefined;
return this.commandService.executeCommand<IDebugAdapterExecutable>(this.debuggerContribution.adapterExecutableCommand, rootFolder).then(ae => {
if (ae) {
return <IAdapterDescriptor>{
type: 'executable',
command: ae.command,
args: ae.args || []
};
}
throw new Error('command adapterExecutableCommand did not return proper command.');
});
}
// fallback: use executable information from package.json
const ae = ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type);
if (ae === undefined) {
throw new Error('no executable specified in package.json');
}
return ae;
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
});
}
substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
if (this.inExtHost()) {
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
});
} else {
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
}
});
}
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
const config = this.configurationService.getValue<ITerminalSettings>('terminal');
return this.configurationManager.runInTerminal(this.inExtHost() ? this.type : '*', args, config);
}
private inExtHost(): boolean {
return true;
return this.configurationManager.runInTerminal(this.type, args, config);
}
get label(): string {
@@ -10,6 +10,7 @@ import * as pfs from 'vs/base/node/pfs';
import { assign } from 'vs/base/common/objects';
import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal';
const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
@@ -314,13 +315,13 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments
let shell: string;
const shell_config = config.integrated.shell;
if (env.isWindows) {
shell = shell_config.windows;
shell = shell_config.windows || getDefaultShell(env.Platform.Windows);
shellType = ShellType.cmd;
} else if (env.isLinux) {
shell = shell_config.linux;
shell = shell_config.linux || getDefaultShell(env.Platform.Linux);
shellType = ShellType.bash;
} else if (env.isMacintosh) {
shell = shell_config.osx;
shell = shell_config.osx || getDefaultShell(env.Platform.Mac);
shellType = ShellType.bash;
} else {
throw new Error('Unknown platform');
@@ -14,7 +14,7 @@ import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugS
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
}
suite('Debug - Model', () => {
@@ -436,7 +436,7 @@ suite('Debug - Model', () => {
// Repl output
test('repl output', () => {
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
const repl = new ReplModel(session);
repl.appendToRepl('first line\n', severity.Error);
repl.appendToRepl('second line ', severity.Error);
@@ -130,7 +130,7 @@ suite('Debug - Debugger', () => {
const testResourcePropertiesService = new TestTextResourcePropertiesService(configurationService);
setup(() => {
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!, undefined!);
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!);
});
teardown(() => {
@@ -19,6 +19,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isNonEmptyArray } from 'vs/base/common/arrays';
export interface IExtensionTemplateData {
icon: HTMLImageElement;
@@ -217,4 +219,46 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
}
}));
}
}
export class ExtensionData implements IExtensionData {
readonly extension: IExtension;
readonly parent: IExtensionData | null;
private readonly getChildrenExtensionIds: (extension: IExtension) => string[];
private readonly childrenExtensionIds: string[];
private readonly extensionsWorkbenchService: IExtensionsWorkbenchService;
constructor(extension: IExtension, parent: IExtensionData | null, getChildrenExtensionIds: (extension: IExtension) => string[], extensionsWorkbenchService: IExtensionsWorkbenchService) {
this.extension = extension;
this.parent = parent;
this.getChildrenExtensionIds = getChildrenExtensionIds;
this.extensionsWorkbenchService = extensionsWorkbenchService;
this.childrenExtensionIds = this.getChildrenExtensionIds(extension);
}
get hasChildren(): boolean {
return isNonEmptyArray(this.childrenExtensionIds);
}
async getChildren(): Promise<IExtensionData[] | null> {
if (this.hasChildren) {
const localById = this.extensionsWorkbenchService.local.reduce((result, e) => { result.set(e.identifier.id.toLowerCase(), e); return result; }, new Map<string, IExtension>());
const result: IExtension[] = [];
const toQuery: string[] = [];
for (const extensionId of this.childrenExtensionIds) {
const id = extensionId.toLowerCase();
const local = localById.get(id);
if (local) {
result.push(local);
} else {
toQuery.push(id);
}
}
const galleryResult = await this.extensionsWorkbenchService.queryGallery({ names: this.childrenExtensionIds, pageSize: this.childrenExtensionIds.length }, CancellationToken.None);
result.push(...galleryResult.firstPage);
return result.map(extension => new ExtensionData(extension, this, this.getChildrenExtensionIds, this.extensionsWorkbenchService));
}
return null;
}
}
@@ -69,14 +69,6 @@ export interface IExtension {
readonly isMalicious: boolean;
}
export interface IExtensionDependencies {
dependencies: IExtensionDependencies[];
hasDependencies: boolean;
identifier: string;
extension: IExtension;
dependent: IExtensionDependencies | null;
}
export const SERVICE_ID = 'extensionsWorkbenchService';
export const IExtensionsWorkbenchService = createDecorator<IExtensionsWorkbenchService>(SERVICE_ID);
@@ -97,7 +89,6 @@ export interface IExtensionsWorkbenchService {
installVersion(extension: IExtension, version: string): Promise<IExtension>;
reinstall(extension: IExtension): Promise<IExtension>;
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null>;
open(extension: IExtension, sideByside?: boolean): Promise<any>;
checkForUpdates(): Promise<void>;
allowedBadgeProviders: string[];
@@ -24,7 +24,7 @@ import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/ex
import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -43,13 +43,13 @@ import { Color } from 'vs/base/common/color';
import { assign } from 'vs/base/common/objects';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionsTree, IExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
import { ExtensionsTree, ExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
import { ShowCurrentReleaseNotesAction } from 'vs/workbench/contrib/update/electron-browser/update';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry';
import { isUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { isUndefined } from 'vs/base/common/types';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
// {{SQL CARBON EDIT}}
@@ -181,7 +181,6 @@ export class ExtensionEditor extends BaseEditor {
private extensionReadme: Cache<string> | null;
private extensionChangelog: Cache<string> | null;
private extensionManifest: Cache<IExtensionManifest | null> | null;
private extensionDependencies: Cache<IExtensionDependencies | null> | null;
private layoutParticipants: ILayoutParticipant[] = [];
private contentDisposables: IDisposable[] = [];
@@ -210,7 +209,6 @@ export class ExtensionEditor extends BaseEditor {
this.extensionReadme = null;
this.extensionChangelog = null;
this.extensionManifest = null;
this.extensionDependencies = null;
}
createEditor(parent: HTMLElement): void {
@@ -298,7 +296,6 @@ export class ExtensionEditor extends BaseEditor {
this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token)));
this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token)));
this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token)));
this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token)));
const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true);
const onError = Event.once(domEvent(this.icon, 'error'));
@@ -643,69 +640,28 @@ export class ExtensionEditor extends BaseEditor {
}
private openDependencies(extension: IExtension): Promise<IActiveElement> {
if (extension.dependencies.length === 0) {
if (arrays.isFalsyOrEmpty(extension.dependencies)) {
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
return Promise.resolve(this.content);
}
return this.loadContents(() => this.extensionDependencies!.get())
.then<IActiveElement, IActiveElement>(extensionDependencies => {
if (extensionDependencies) {
const content = $('div', { class: 'subcontent' });
const scrollableContent = new DomScrollableElement(content, {});
append(this.content, scrollableContent.getDomNode());
this.contentDisposables.push(scrollableContent);
const content = $('div', { class: 'subcontent' });
const scrollableContent = new DomScrollableElement(content, {});
append(this.content, scrollableContent.getDomNode());
this.contentDisposables.push(scrollableContent);
const dependenciesTree = this.renderDependencies(content, extensionDependencies);
const layout = () => {
scrollableContent.scanDomNode();
const scrollDimensions = scrollableContent.getScrollDimensions();
dependenciesTree.layout(scrollDimensions.height);
};
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content);
const layout = () => {
scrollableContent.scanDomNode();
const scrollDimensions = scrollableContent.getScrollDimensions();
dependenciesTree.layout(scrollDimensions.height);
};
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
this.contentDisposables.push(dependenciesTree);
scrollableContent.scanDomNode();
return { focus() { dependenciesTree.domFocus(); } };
} else {
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
return Promise.resolve(this.content);
}
}, error => {
append(this.content, $('p.nocontent')).textContent = error;
this.notificationService.error(error);
return this.content;
});
}
private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): ExtensionsTree {
class ExtensionData implements IExtensionData {
private readonly extensionDependencies: IExtensionDependencies;
constructor(extensionDependencies: IExtensionDependencies) {
this.extensionDependencies = extensionDependencies;
}
get extension(): IExtension {
return this.extensionDependencies.extension;
}
get parent(): IExtensionData | null {
return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
}
get hasChildren(): boolean {
return this.extensionDependencies.hasDependencies;
}
getChildren(): Promise<IExtensionData[] | null> {
return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : Promise.resolve(null);
}
}
return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extensionDependencies), container);
this.contentDisposables.push(dependenciesTree);
scrollableContent.scanDomNode();
return Promise.resolve({ focus() { dependenciesTree.domFocus(); } });
}
private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
@@ -714,7 +670,7 @@ export class ExtensionEditor extends BaseEditor {
append(this.content, scrollableContent.getDomNode());
this.contentDisposables.push(scrollableContent);
const extensionsPackTree = this.renderExtensionPack(content, extension);
const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content);
const layout = () => {
scrollableContent.scanDomNode();
const scrollDimensions = scrollableContent.getScrollDimensions();
@@ -728,35 +684,6 @@ export class ExtensionEditor extends BaseEditor {
return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
}
private renderExtensionPack(container: HTMLElement, extension: IExtension): ExtensionsTree {
const extensionsWorkbenchService = this.extensionsWorkbenchService;
class ExtensionData implements IExtensionData {
readonly extension: IExtension;
readonly parent: IExtensionData | null;
constructor(extension: IExtension, parent?: IExtensionData) {
this.extension = extension;
this.parent = withUndefinedAsNull(parent);
}
get hasChildren(): boolean {
return this.extension.extensionPack.length > 0;
}
getChildren(): Promise<IExtensionData[] | null> {
if (this.hasChildren) {
const names = arrays.distinct(this.extension.extensionPack, e => e.toLowerCase());
return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length }, CancellationToken.None)
.then(result => result.firstPage.map(extension => new ExtensionData(extension, this)));
}
return Promise.resolve(null);
}
}
return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension), container);
}
private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
const contributes = manifest.contributes;
const configuration = contributes && contributes.configuration;
@@ -1558,7 +1558,7 @@ export class InstallExtensionsAction extends OpenExtensionsViewletAction {
export class ShowEnabledExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.showEnabledExtensions';
static LABEL = localize('showEnabledExtensions', 'Show Enabled Extensions');
static LABEL = localize('showEnabledExtensions', "Show Enabled Extensions");
constructor(
id: string,
@@ -23,8 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IWindowService } from 'vs/platform/windows/common/windows';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
// {{SQL CARBON EDIT}}
import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
@@ -323,53 +322,6 @@ ${this.description}
}
}
class ExtensionDependencies implements IExtensionDependencies {
private _hasDependencies: boolean | null = null;
constructor(private _extension: IExtension, private _identifier: string, private _map: Map<string, IExtension>, private _dependent: IExtensionDependencies | null = null) { }
get hasDependencies(): boolean {
if (this._hasDependencies === null) {
this._hasDependencies = this.computeHasDependencies();
}
return this._hasDependencies;
}
get extension(): IExtension {
return this._extension;
}
get identifier(): string {
return this._identifier;
}
get dependent(): IExtensionDependencies | null {
return this._dependent;
}
get dependencies(): IExtensionDependencies[] {
if (!this.hasDependencies) {
return [];
}
return this._extension.dependencies.map(id => new ExtensionDependencies(this._map.get(id)!, id, this._map, this));
}
private computeHasDependencies(): boolean {
if (this._extension && this._extension.dependencies.length > 0) {
let dependent = this._dependent;
while (dependent !== null) {
if (dependent.identifier === this.identifier) {
return false;
}
dependent = dependent.dependent;
}
return true;
}
return false;
}
}
class Extensions extends Disposable {
private readonly _onChange: Emitter<Extension | undefined> = new Emitter<Extension | undefined>();
@@ -666,27 +618,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
});
}
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null> {
if (!extension.dependencies.length) {
return Promise.resolve(null);
}
return this.extensionService.getExtensionsReport()
.then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => ({ id })), token)
.then(galleryExtensions => {
const extensions: IExtension[] = [...this.local, ...galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet))];
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.identifier.id, extension);
}
return new ExtensionDependencies(extension, extension.identifier.id, map);
});
});
}
open(extension: IExtension, sideByside: boolean = false): Promise<any> {
return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP));
}
@@ -480,248 +480,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
assert.ok(target.calledOnce);
});
test('test extension dependencies when empty', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
return testObject.queryGallery(CancellationToken.None).then(page => {
return testObject.loadDependencies(page.firstPage[0], CancellationToken.None).then(dependencies => {
assert.equal(null, dependencies);
});
});
});
test('test one level extension dependencies without cycle', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c', 'pub.d'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('c'), aGalleryExtension('d')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(3, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0];
assert.ok(!actual.hasDependencies);
assert.equal('pub.b', actual.extension.identifier.id);
assert.equal('pub.b', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1];
assert.ok(!actual.hasDependencies);
assert.equal('pub.c', actual.extension.identifier.id);
assert.equal('pub.c', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[2];
assert.ok(!actual.hasDependencies);
assert.equal('pub.d', actual.extension.identifier.id);
assert.equal('pub.d', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test one level extension dependencies with cycle', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('a')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(2, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.b', actual.extension.identifier.id);
assert.equal('pub.b', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.a', actual.extension.identifier.id);
assert.equal('pub.a', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test one level extension dependencies with missing dependencies', async () => {
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(2, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0]!;
assert.ok(!actual.hasDependencies);
assert.equal(null, actual.extension);
assert.equal('pub.b', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.a', actual.extension.identifier.id);
assert.equal('pub.a', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test one level extension dependencies with in built dependencies', async () => {
const local = aLocalExtension('inbuilt', {}, { type: ExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.inbuilt', 'pub.a'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
assert.ok(actual!.hasDependencies);
assert.equal(extension, actual!.extension);
assert.equal(null, actual!.dependent);
assert.equal(2, actual!.dependencies.length);
assert.equal('pub.a', actual!.identifier);
let dependent = actual;
actual = dependent!.dependencies[0]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.inbuilt', actual.extension.identifier.id);
assert.equal('pub.inbuilt', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
actual = dependent!.dependencies[1]!;
assert.ok(!actual.hasDependencies);
assert.equal('pub.a', actual.extension.identifier.id);
assert.equal('pub.a', actual.identifier);
assert.equal(dependent, actual.dependent);
assert.equal(0, actual.dependencies.length);
});
});
});
test('test more than one level of extension dependencies', async () => {
const local = aLocalExtension('c', { extensionDependencies: ['pub.d'] }, { type: ExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
testObject = await aWorkbenchService();
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c'] })));
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [
aGalleryExtension('b', {}, { dependencies: ['pub.d', 'pub.e'] }),
aGalleryExtension('d', {}, { dependencies: ['pub.f', 'pub.c'] }),
aGalleryExtension('e')]);
return testObject.queryGallery(CancellationToken.None).then(page => {
const extension = page.firstPage[0];
return testObject.loadDependencies(extension, CancellationToken.None).then(a => {
assert.ok(a!.hasDependencies);
assert.equal(extension, a!.extension);
assert.equal(null, a!.dependent);
assert.equal(2, a!.dependencies.length);
assert.equal('pub.a', a!.identifier);
let b = a!.dependencies[0];
assert.ok(b.hasDependencies);
assert.equal('pub.b', b.extension.identifier.id);
assert.equal('pub.b', b.identifier);
assert.equal(a, b.dependent);
assert.equal(2, b.dependencies.length);
let c = a!.dependencies[1];
assert.ok(c.hasDependencies);
assert.equal('pub.c', c.extension.identifier.id);
assert.equal('pub.c', c.identifier);
assert.equal(a, c.dependent);
assert.equal(1, c.dependencies.length);
let d = b.dependencies[0];
assert.ok(d.hasDependencies);
assert.equal('pub.d', d.extension.identifier.id);
assert.equal('pub.d', d.identifier);
assert.equal(b, d.dependent);
assert.equal(2, d.dependencies.length);
let e = b.dependencies[1];
assert.ok(!e.hasDependencies);
assert.equal('pub.e', e.extension.identifier.id);
assert.equal('pub.e', e.identifier);
assert.equal(b, e.dependent);
assert.equal(0, e.dependencies.length);
let f = d.dependencies[0];
assert.ok(!f.hasDependencies);
assert.equal(null, f.extension);
assert.equal('pub.f', f.identifier);
assert.equal(d, f.dependent);
assert.equal(0, f.dependencies.length);
c = d.dependencies[1];
assert.ok(c.hasDependencies);
assert.equal('pub.c', c.extension.identifier.id);
assert.equal('pub.c', c.identifier);
assert.equal(d, c.dependent);
assert.equal(1, c.dependencies.length);
d = c.dependencies[0];
assert.ok(!d.hasDependencies);
assert.equal('pub.d', d.extension.identifier.id);
assert.equal('pub.d', d.identifier);
assert.equal(c, d.dependent);
assert.equal(0, d.dependencies.length);
c = a!.dependencies[1];
d = c.dependencies[0];
assert.ok(d.hasDependencies);
assert.equal('pub.d', d.extension.identifier.id);
assert.equal('pub.d', d.identifier);
assert.equal(c, d.dependent);
assert.equal(2, d.dependencies.length);
f = d.dependencies[0];
assert.ok(!f.hasDependencies);
assert.equal(null, f.extension);
assert.equal('pub.f', f.identifier);
assert.equal(d, f.dependent);
assert.equal(0, f.dependencies.length);
c = d.dependencies[1];
assert.ok(!c.hasDependencies);
assert.equal('pub.c', c.extension.identifier.id);
assert.equal('pub.c', c.identifier);
assert.equal(d, c.dependent);
assert.equal(0, c.dependencies.length);
});
});
});
test('test uninstalled extensions are always enabled', async () => {
return instantiationService.get(IExtensionEnablementService).setEnablement([aLocalExtension('b')], EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement([aLocalExtension('c')], EnablementState.WorkspaceDisabled))
@@ -94,7 +94,7 @@ CommandsRegistry.registerCommand({
const directoriesToOpen = distinct(stats.filter(data => data.success).map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath)));
return directoriesToOpen.map(dir => {
if (configurationService.getValue<IExternalTerminalConfiguration>().terminal.explorerKind === 'integrated') {
const instance = integratedTerminalService.createTerminal({ cwd: dir }, true);
const instance = integratedTerminalService.createTerminal({ cwd: dir });
if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) {
integratedTerminalService.setActiveInstance(instance);
integratedTerminalService.showPanel(true);
@@ -196,7 +196,7 @@ export const tocData: ITOCEntry = {
]
};
export const knownAcronyms = new Set();
export const knownAcronyms = new Set<string>();
[
'css',
'html',
@@ -209,3 +209,7 @@ export const knownAcronyms = new Set();
'id',
'php',
].forEach(str => knownAcronyms.add(str));
export const knownTermMappings = new Map<string, string>();
knownTermMappings.set('power shell', 'PowerShell');
knownTermMappings.set('powershell', 'PowerShell');
@@ -11,7 +11,7 @@ import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
import { ITOCEntry, knownAcronyms } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
@@ -404,8 +404,8 @@ export function settingKeyToDisplayFormat(key: string, groupId = ''): { category
}
function wordifyKey(key: string): string {
return key
.replace(/\.([a-z0-9])/g, (match, p1) => ` ${p1.toUpperCase()}`) // Replace dot with spaced '>'
key = key
.replace(/\.([a-z0-9])/g, (_, p1) => ` ${p1.toUpperCase()}`) // Replace dot with spaced '>'
.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar
.replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo
.replace(/\b\w+\b/g, match => { // Upper casing known acronyms
@@ -413,6 +413,12 @@ function wordifyKey(key: string): string {
match.toUpperCase() :
match;
});
for (let [k, v] of knownTermMappings) {
key = key.replace(new RegExp(`\\b${k}\\b`, 'gi'), v);
}
return key;
}
function trimCategoryForGroup(category: string, groupId: string): string {
@@ -1225,8 +1225,14 @@ export class SettingsEditor2 extends BaseEditor {
return;
}
this.clearFilterLinkContainer.style.display = this.viewState.tagFilters && this.viewState.tagFilters.size > 0
? 'initial'
: 'none';
if (!this.searchResultModel) {
this.countElement.style.display = 'none';
DOM.removeClass(this.rootElement, 'no-results');
return;
}
if (this.tocTreeModel && this.tocTreeModel.settingsTreeRoot) {
@@ -1239,9 +1245,6 @@ export class SettingsEditor2 extends BaseEditor {
this.countElement.style.display = 'block';
DOM.toggleClass(this.rootElement, 'no-results', count === 0);
this.clearFilterLinkContainer.style.display = this.viewState.tagFilters && this.viewState.tagFilters.size > 0
? 'initial'
: 'none';
}
}
@@ -35,6 +35,20 @@ suite('SettingsTree', () => {
category: '',
label: 'Foo'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.1leading.number'),
{
category: 'Foo 1leading',
label: 'Number'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.1Leading.number'),
{
category: 'Foo 1 Leading',
label: 'Number'
});
});
test('settingKeyToDisplayFormat - with category', () => {
@@ -101,19 +115,21 @@ suite('SettingsTree', () => {
category: 'Something Else',
label: 'Etc'
});
});
test('settingKeyToDisplayFormat - known acronym/term', () => {
assert.deepEqual(
settingKeyToDisplayFormat('foo.1leading.number'),
settingKeyToDisplayFormat('css.someCssSetting'),
{
category: 'Foo 1leading',
label: 'Number'
category: 'CSS',
label: 'Some CSS Setting'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.1Leading.number'),
settingKeyToDisplayFormat('powershell.somePowerShellSetting'),
{
category: 'Foo 1 Leading',
label: 'Number'
category: 'PowerShell',
label: 'Some PowerShell Setting'
});
});
@@ -0,0 +1,428 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { RemoteExtensionLogFileName, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { MenuId, IMenuService, MenuItemAction, IMenu } from 'vs/platform/actions/common/actions';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output';
import * as resources from 'vs/base/common/resources';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
import { ILabelService } from 'vs/platform/label/common/label';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { ILogService } from 'vs/platform/log/common/log';
import { IFileService } from 'vs/platform/files/common/files';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc';
import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc';
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
import { ipcRenderer as ipc } from 'electron';
import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IProgressService2, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import Severity from 'vs/base/common/severity';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions/windowActions';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions';
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
private windowCommandMenu: IMenu;
private hasWindowActions: boolean = false;
private remoteAuthority: string | undefined;
private disconnected: boolean = false;
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ILabelService private readonly labelService: ILabelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IMenuService private menuService: IMenuService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ICommandService private readonly commandService: ICommandService,
@IExtensionService extensionService: IExtensionService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
this._register(this.windowCommandMenu);
this._register(CommandsRegistry.registerCommand(WINDOW_ACTIONS_COMMAND_ID, _ => this.showIndicatorActions(this.windowCommandMenu)));
this.remoteAuthority = environmentService.configuration.remoteAuthority;
if (this.remoteAuthority) {
// Pending entry until extensions are ready
this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."));
}
extensionService.whenInstalledExtensionsRegistered().then(_ => {
if (this.remoteAuthority) {
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
}
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
this.updateWindowIndicator();
});
const connection = remoteAgentService.getConnection();
if (connection) {
this._register(connection.onDidStateChange((e) => {
switch (e.type) {
case PersistenConnectionEventType.ConnectionLost:
case PersistenConnectionEventType.ReconnectionPermanentFailure:
case PersistenConnectionEventType.ReconnectionRunning:
case PersistenConnectionEventType.ReconnectionWait:
this.setDisconnected(true);
break;
case PersistenConnectionEventType.ConnectionGain:
this.setDisconnected(false);
break;
}
}));
}
}
private setDisconnected(isDisconnected: boolean): void {
if (this.disconnected !== isDisconnected) {
this.disconnected = isDisconnected;
this.updateWindowIndicator();
}
}
private updateWindowIndicator(): void {
const windowActionCommand = this.windowCommandMenu.getActions().length ? WINDOW_ACTIONS_COMMAND_ID : undefined;
if (this.remoteAuthority) {
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
if (!this.disconnected) {
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
} else {
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel));
}
} else {
if (windowActionCommand) {
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
} else if (this.windowIndicatorEntry) {
this.windowIndicatorEntry.dispose();
this.windowIndicatorEntry = undefined;
}
}
}
private updateWindowActions() {
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
if (newHasWindowActions !== this.hasWindowActions) {
this.hasWindowActions = newHasWindowActions;
this.updateWindowIndicator();
}
}
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
const properties: IStatusbarEntry = {
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
};
if (this.windowIndicatorEntry) {
this.windowIndicatorEntry.update(properties);
} else {
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
}
}
private showIndicatorActions(menu: IMenu) {
const actions = menu.getActions();
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
for (let actionGroup of actions) {
if (items.length) {
items.push({ type: 'separator' });
}
for (let action of actionGroup[1]) {
if (action instanceof MenuItemAction) {
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
if (action.item.category) {
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
label = nls.localize('cat.title', "{0}: {1}", category, label);
}
items.push({
type: 'item',
id: action.item.id,
label
});
}
}
}
const quickPick = this.quickInputService.createQuickPick();
quickPick.items = items;
quickPick.canSelectMany = false;
quickPick.onDidAccept(_ => {
const selectedItems = quickPick.selectedItems;
if (selectedItems.length === 1) {
this.commandService.executeCommand(selectedItems[0].id!);
}
quickPick.hide();
});
quickPick.show();
}
}
class LogOutputChannels extends Disposable implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
super();
remoteAgentService.getEnvironment().then(remoteEnv => {
if (remoteEnv) {
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels);
outputChannelRegistry.registerChannel({ id: 'remoteExtensionLog', label: nls.localize('remoteExtensionLog', "Remote Server"), file: resources.joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`), log: true });
}
});
}
}
class RemoteChannelsContribution implements IWorkbenchContribution {
constructor(
@ILogService logService: ILogService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IFileService fileService: IFileService,
@IDialogService dialogService: IDialogService
) {
const connection = remoteAgentService.getConnection();
if (connection) {
connection.registerChannel('dialog', new DialogChannel(dialogService));
connection.registerChannel('download', new DownloadServiceChannel());
connection.registerChannel('loglevel', new LogLevelSetterChannel(logService));
}
}
}
class RemoteAgentDiagnosticListener implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ILabelService labelService: ILabelService
) {
ipc.on('vscode:getDiagnosticInfo', (event: Event, request: { replyChannel: string, args: IDiagnosticInfoOptions }): void => {
const connection = remoteAgentService.getConnection();
if (connection) {
const hostName = labelService.getHostLabel(REMOTE_HOST_SCHEME, connection.remoteAuthority);
remoteAgentService.getDiagnosticInfo(request.args)
.then(info => {
if (info) {
(info as IRemoteDiagnosticInfo).hostName = hostName;
}
ipc.send(request.replyChannel, info);
})
.catch(e => {
const errorMessage = e && e.message ? `Fetching remote diagnostics for '${hostName}' failed: ${e.message}` : `Fetching remote diagnostics for '${hostName}' failed.`;
ipc.send(request.replyChannel, { hostName, errorMessage });
});
} else {
ipc.send(request.replyChannel);
}
});
}
}
class ProgressReporter {
private _currentProgress: IProgress<IProgressStep> | null = null;
private lastReport: string | null = null;
constructor(currentProgress: IProgress<IProgressStep> | null) {
this._currentProgress = currentProgress;
}
set currentProgress(progress: IProgress<IProgressStep>) {
this._currentProgress = progress;
}
report(message?: string) {
if (message) {
this.lastReport = message;
}
if (this.lastReport && this._currentProgress) {
this._currentProgress.report({ message: this.lastReport });
}
}
}
class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IProgressService2 progressService: IProgressService2,
@IDialogService dialogService: IDialogService,
@ICommandService commandService: ICommandService
) {
const connection = remoteAgentService.getConnection();
if (connection) {
let currentProgressPromiseResolve: (() => void) | null = null;
let progressReporter: ProgressReporter | null = null;
let currentTimer: ReconnectionTimer | null = null;
connection.onDidStateChange((e) => {
if (currentTimer) {
currentTimer.dispose();
currentTimer = null;
}
switch (e.type) {
case PersistenConnectionEventType.ConnectionLost:
if (!currentProgressPromiseResolve) {
let promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
progressService!.withProgress(
{ location: ProgressLocation.Dialog },
(progress: IProgress<IProgressStep> | null) => { progressReporter = new ProgressReporter(progress!); return promise; },
() => {
currentProgressPromiseResolve!();
promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
progressService!.withProgress({ location: ProgressLocation.Notification }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; });
progressReporter!.report();
}
);
}
progressReporter!.report(nls.localize('connectionLost', "Connection Lost"));
break;
case PersistenConnectionEventType.ReconnectionWait:
currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds);
break;
case PersistenConnectionEventType.ReconnectionRunning:
progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect..."));
break;
case PersistenConnectionEventType.ReconnectionPermanentFailure:
currentProgressPromiseResolve!();
currentProgressPromiseResolve = null;
progressReporter = null;
dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(choice => {
// Reload the window
if (choice === 0) {
commandService.executeCommand(ReloadWindowAction.ID);
}
});
break;
case PersistenConnectionEventType.ConnectionGain:
currentProgressPromiseResolve!();
currentProgressPromiseResolve = null;
progressReporter = null;
break;
}
});
}
}
}
class ReconnectionTimer implements IDisposable {
private readonly _progressReporter: ProgressReporter;
private readonly _completionTime: number;
private readonly _token: NodeJS.Timeout;
constructor(progressReporter: ProgressReporter, completionTime: number) {
this._progressReporter = progressReporter;
this._completionTime = completionTime;
this._token = setInterval(() => this._render(), 1000);
this._render();
}
public dispose(): void {
clearInterval(this._token);
}
private _render() {
const remainingTimeMs = this._completionTime - Date.now();
if (remainingTimeMs < 0) {
return;
}
const remainingTime = Math.ceil(remainingTimeMs / 1000);
if (remainingTime === 1) {
this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime));
} else {
this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime));
}
}
}
class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchContribution {
constructor(
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.updateRemoteTelemetryEnablement();
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('telemetry.enableTelemetry')) {
this.updateRemoteTelemetryEnablement();
}
}));
}
private updateRemoteTelemetryEnablement(): Promise<void> {
if (!this.configurationService.getValue('telemetry.enableTelemetry')) {
return this.remoteAgentService.disableTelemetry();
}
return Promise.resolve();
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchContributionsExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
id: 'remote',
title: nls.localize('remote', "Remote"),
type: 'object',
properties: {
'remote.extensionKind': {
type: 'object',
markdownDescription: nls.localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."),
patternProperties: {
'([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': {
type: 'string',
enum: [
'ui',
'workspace'
],
enumDescriptions: [
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
],
default: 'ui'
},
},
default: {
'pub.name': 'ui'
}
}
}
});
@@ -342,6 +342,9 @@ export class SearchView extends ViewletPanel {
return this.inputPatternExcludes;
}
/**
* Warning: a bit expensive due to updating the view title
*/
protected updateActions(): void {
for (const action of this.actions) {
action.update();
@@ -1386,6 +1389,8 @@ export class SearchView extends ViewletPanel {
let visibleMatches = 0;
let updatedActionsForFileCount = false;
// Handle UI updates in an interval to show frequent progress and results
const uiRefreshHandle: any = setInterval(() => {
if (!this.searching) {
@@ -1399,7 +1404,9 @@ export class SearchView extends ViewletPanel {
visibleMatches = fileCount;
this.refreshAndUpdateCount();
}
if (fileCount > 0) {
if (fileCount > 0 && !updatedActionsForFileCount) {
updatedActionsForFileCount = true;
this.updateActions();
}
}, 100);
@@ -274,7 +274,7 @@ export class QueryBuilder {
* Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths
*/
private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] {
if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || !searchPaths || !searchPaths.length) {
if (!searchPaths || !searchPaths.length) {
// No workspace => ignore search paths
return [];
}
@@ -35,6 +35,7 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export const TERMINAL_PICKER_PREFIX = 'term ';
@@ -85,7 +86,7 @@ export class ToggleTerminalAction extends TogglePanelAction {
if (this.terminalService.terminalInstances.length === 0) {
// If there is not yet an instance attempt to create it here so that we can suggest a
// new shell on Windows (and not do so when the panel is restored on reload).
const newTerminalInstance = this.terminalService.createTerminal(undefined, true);
const newTerminalInstance = this.terminalService.createTerminal(undefined);
const toDispose = newTerminalInstance.onProcessIdReady(() => {
newTerminalInstance.focus();
toDispose.dispose();
@@ -329,7 +330,7 @@ export class CreateNewTerminalAction extends Action {
if (folders.length <= 1) {
// Allow terminal service to handle the path when there is only a
// single root
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined, true));
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined));
} else {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal")
@@ -339,7 +340,7 @@ export class CreateNewTerminalAction extends Action {
// Don't create the instance if the workspace picker was canceled
return null;
}
return this.terminalService.createTerminal({ cwd: workspace.uri }, true);
return this.terminalService.createTerminal({ cwd: workspace.uri });
});
}
@@ -366,7 +367,7 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action {
}
public run(event?: any): Promise<any> {
const instance = this.terminalService.createTerminal(undefined, true);
const instance = this.terminalService.createTerminal(undefined);
if (!instance) {
return Promise.resolve(undefined);
}
@@ -720,6 +721,14 @@ export class SwitchTerminalAction extends Action {
if (!item || !item.split) {
return Promise.resolve(null);
}
if (item === SwitchTerminalActionViewItem.SEPARATOR) {
this.terminalService.refreshActiveTab();
return Promise.resolve(null);
}
if (item === SelectDefaultShellWindowsTerminalAction.LABEL) {
this.terminalService.refreshActiveTab();
return this.terminalService.selectDefaultWindowsShell();
}
const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1;
this.terminalService.setActiveTabByIndex(selectedTabIndex);
return this.terminalService.showPanel(true);
@@ -728,11 +737,14 @@ export class SwitchTerminalAction extends Action {
export class SwitchTerminalActionViewItem extends SelectActionViewItem {
public static readonly SEPARATOR = '─────────';
constructor(
action: IAction,
@ITerminalService private readonly terminalService: ITerminalService,
@IThemeService themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService
@IContextViewService contextViewService: IContextViewService,
@IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService
) {
super(null, action, terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') });
@@ -743,7 +755,13 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
}
private _updateItems(): void {
this.setOptions(this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), this.terminalService.activeTabIndex);
const items = this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows;
if (enableSelectDefaultShell) {
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR });
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
}
this.setOptions(items, this.terminalService.activeTabIndex);
}
}
@@ -635,7 +635,7 @@ export class TerminalInstance implements ITerminalInstance {
private _measureRenderTime(): void {
const frameTimes: number[] = [];
const textRenderLayer = this._xterm._core.renderer._renderLayers[0];
const textRenderLayer = this._xterm._core._renderCoordinator._renderer._renderLayers[0];
const originalOnGridChanged = textRenderLayer.onGridChanged;
const evaluateCanvasRenderer = () => {
@@ -647,11 +647,7 @@ export class TerminalInstance implements ITerminalInstance {
const promptChoices: IPromptChoice[] = [
{
label: nls.localize('yes', "Yes"),
run: () => {
this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER).then(() => {
this._notificationService.info(nls.localize('terminal.rendererInAllNewTerminals', "The terminal is now using the fallback renderer."));
});
}
run: () => this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER)
} as IPromptChoice,
{
label: nls.localize('no', "No"),
@@ -1264,7 +1260,7 @@ export class TerminalInstance implements ITerminalInstance {
// maximize on Windows/Linux would fire an event saying that the terminal was not
// visible.
if (this._xterm.getOption('rendererType') === 'canvas') {
this._xterm._core.renderer.onIntersectionChange({ intersectionRatio: 1 });
this._xterm._core._renderCoordinator._onIntersectionChange({ intersectionRatio: 1 });
// HACK: Force a refresh of the screen to ensure links are refresh corrected.
// This can probably be removed when the above hack is fixed in Chromium.
this._xterm.refresh(0, this._xterm.rows - 1);
@@ -88,6 +88,9 @@ export class TerminalLinkHandler {
this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/;
this._tooltipCallback = (e: MouseEvent) => {
if (!this._widgetManager) {
return;
}
if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') {
const target = (e.target as HTMLElement);
this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString());
@@ -115,7 +118,11 @@ export class TerminalLinkHandler {
const options: ILinkMatcherOptions = {
matchIndex,
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: CUSTOM_LINK_PRIORITY
};
@@ -132,7 +139,11 @@ export class TerminalLinkHandler {
this._xterm.webLinksInit(wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e)
});
}
@@ -144,7 +155,11 @@ export class TerminalLinkHandler {
this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
});
@@ -158,7 +173,11 @@ export class TerminalLinkHandler {
matchIndex: 1,
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
};
@@ -242,12 +261,16 @@ export class TerminalLinkHandler {
private _getLinkHoverString(): string {
const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
if (editorConf.multiCursorModifier === 'ctrlCmd') {
return nls.localize('terminalLinkHandler.followLinkAlt', 'Alt + click to follow link');
if (platform.isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkAlt.mac', "Option + click to follow link");
} else {
return nls.localize('terminalLinkHandler.followLinkAlt', "Alt + click to follow link");
}
}
if (platform.isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkCmd', 'Cmd + click to follow link');
return nls.localize('terminalLinkHandler.followLinkCmd', "Cmd + click to follow link");
}
return nls.localize('terminalLinkHandler.followLinkCtrl', 'Ctrl + click to follow link');
return nls.localize('terminalLinkHandler.followLinkCtrl', "Ctrl + click to follow link");
}
private get osPath(): IPath {
@@ -3,21 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
@@ -36,7 +34,6 @@ export abstract class TerminalService extends CommonTerminalService implements I
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
@@ -52,7 +49,7 @@ export abstract class TerminalService extends CommonTerminalService implements I
return instance;
}
public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance {
public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance {
const terminalTab = this._instantiationService.createInstance(TerminalTab,
this._terminalFocusContextKey,
this.configHelper,
@@ -68,74 +65,9 @@ export abstract class TerminalService extends CommonTerminalService implements I
this.setActiveInstanceByIndex(0);
}
this._onInstancesChanged.fire();
this._suggestShellChange(wasNewTerminalAction);
return instance;
}
private _suggestShellChange(wasNewTerminalAction?: boolean): void {
// Only suggest on Windows since $SHELL works great for macOS/Linux
if (!platform.isWindows) {
return;
}
if (this._environmentService.configuration.remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Only suggest when the terminal instance is being created by an explicit user action to
// launch a terminal, as opposed to something like tasks, debug, panel restore, etc.
if (!wasNewTerminalAction) {
return;
}
if (this._environmentService.configuration.remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Don't suggest if the user has explicitly opted out
const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false);
if (neverSuggest) {
return;
}
// Never suggest if the setting is non-default already (ie. they set the setting manually)
if (this.configHelper.config.shell.windows !== this.getDefaultShell(platform.Platform.Windows)) {
this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL);
return;
}
this._notificationService.prompt(
Severity.Info,
nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."),
[{
label: nls.localize('customize', "Customize"),
run: () => {
this.selectDefaultWindowsShell().then(shell => {
if (!shell) {
return Promise.resolve(null);
}
// Launch a new instance with the newly selected shell
const instance = this.createTerminal({
executable: shell,
args: this.configHelper.config.shellArgs.windows
});
if (instance) {
this.setActiveInstance(instance);
}
return Promise.resolve(null);
});
}
},
{
label: nls.localize('never again', "Don't Show Again"),
isSecondary: true,
run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL)
}]
);
}
public focusFindWidget(): Promise<void> {
return this.showPanel(false).then(() => {
const panel = this._panelService.getActivePanel() as TerminalPanel;
@@ -37,7 +37,6 @@ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED = new RawContextKey
export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated();
export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed';
export const NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY = 'terminal.integrated.neverSuggestSelectWindowsShell';
export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime';
// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of
@@ -64,9 +63,9 @@ export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '50
export interface ITerminalConfiguration {
shell: {
linux: string;
osx: string;
windows: string;
linux: string | null;
osx: string | null;
windows: string | null;
};
shellArgs: {
linux: string[];
@@ -216,10 +215,8 @@ export interface ITerminalService {
/**
* Creates a terminal.
* @param shell The shell launch configuration to use.
* @param wasNewTerminalAction Whether this was triggered by a new terminal action, if so a
* default shell selection dialog may display.
*/
createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
createTerminal(shell?: IShellLaunchConfig): ITerminalInstance;
/**
* Creates a terminal renderer.
@@ -245,6 +242,12 @@ export interface ITerminalService {
setActiveTabToPrevious(): void;
setActiveTabByIndex(tabIndex: number): void;
/**
* Fire the onActiveTabChanged event, this will trigger the terminal dropdown to be updated,
* among other things.
*/
refreshActiveTab(): void;
showPanel(focus?: boolean): Promise<void>;
hidePanel(): void;
focusFindWidget(): Promise<void>;
@@ -98,6 +98,7 @@ function _getLangEnvVariable(locale?: string) {
es: 'ES',
fi: 'FI',
fr: 'FR',
hu: 'HU',
it: 'IT',
ja: 'JP',
ko: 'KR',
@@ -41,7 +41,7 @@ export abstract class TerminalService implements ITerminalService {
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; }
private readonly _onActiveTabChanged = new Emitter<void>();
protected readonly _onActiveTabChanged = new Emitter<void>();
public get onActiveTabChanged(): Event<void> { return this._onActiveTabChanged.event; }
protected readonly _onInstanceCreated = new Emitter<ITerminalInstance>();
public get onInstanceCreated(): Event<ITerminalInstance> { return this._onInstanceCreated.event; }
@@ -104,6 +104,7 @@ export abstract class TerminalService implements ITerminalService {
protected abstract _getWslPath(path: string): Promise<string>;
protected abstract _getWindowsBuildNumber(): number;
public abstract refreshActiveTab(): void;
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
public abstract getDefaultShell(platform: Platform): string;
@@ -20,7 +20,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ipcRenderer as ipc } from 'electron';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
import { coalesce } from 'vs/base/common/arrays';
@@ -45,11 +44,10 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IExtensionService extensionService: IExtensionService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, environmentService, extensionService, fileService, remoteAgentService);
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, extensionService, fileService, remoteAgentService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro);
ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => {
@@ -102,6 +100,11 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
return getDefaultShell(p);
}
public refreshActiveTab(): void {
// Fire active instances changed
this._onActiveTabChanged.fire();
}
public selectDefaultWindowsShell(): Promise<string | undefined> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
@@ -62,13 +62,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
!is32ProcessOn64Windows &&
getWindowsBuildNumber() >= 18309;
const options: pty.IPtyForkOptions = {
const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = {
name: shellName,
cwd,
env,
cols,
rows,
experimentalUseConpty: useConpty
experimentalUseConpty: useConpty,
conptyInheritCursor: true
};
// TODO: Need to verify whether executable is on $PATH, otherwise things like cmd.exe will break
@@ -91,14 +92,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
this._processStartupComplete = new Promise<void>(c => {
this.onProcessIdReady(() => c());
});
ptyProcess.on('data', (data) => {
ptyProcess.on('data', data => {
this._onProcessData.fire(data);
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
this._queueProcessExit();
}
});
ptyProcess.on('exit', (code) => {
ptyProcess.on('exit', code => {
this._exitCode = code;
this._queueProcessExit();
});
@@ -126,12 +127,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
setTimeout(() => {
this._sendProcessTitle(ptyProcess);
}, 0);
// Setup polling
this._titleInterval = setInterval(() => {
if (this._currentTitle !== ptyProcess.process) {
this._sendProcessTitle(ptyProcess);
}
}, 200);
// Setup polling for non-Windows, for Windows `process` doesn't change
if (!platform.isWindows) {
this._titleInterval = setInterval(() => {
if (this._currentTitle !== ptyProcess.process) {
this._sendProcessTitle(ptyProcess);
}
}, 200);
}
}
// Allow any trailing data events to be sent before the exit event is sent.
@@ -189,7 +192,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
if (this._isDisposed || !this._ptyProcess) {
return;
}
this._logService.trace('IPty#write', data);
this._logService.trace('IPty#write', `${data.length} characters`);
this._ptyProcess.write(data);
}
@@ -16,7 +16,7 @@ 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 { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
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';
@@ -118,7 +118,7 @@ class WebviewProtocolProvider extends Disposable {
private readonly _extensionLocation: URI | undefined,
private readonly _getLocalResourceRoots: () => ReadonlyArray<URI>,
private readonly _environmentService: IEnvironmentService,
private readonly _textFileService: ITextFileService,
private readonly _fileService: IFileService,
) {
super();
@@ -137,11 +137,11 @@ class WebviewProtocolProvider extends Disposable {
const appRootUri = URI.file(this._environmentService.appRoot);
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._textFileService, undefined, () => [
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, undefined, () => [
appRootUri
]);
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._textFileService, this._extensionLocation, () =>
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () =>
this._getLocalResourceRoots()
);
}
@@ -165,7 +165,8 @@ class WebviewPortMappingProvider extends Disposable {
session.onBeforeRequest(async (details) => {
const uri = URI.parse(details.url);
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
const allowedSchemes = ['http', 'https', 'ws', 'wss'];
if (allowedSchemes.indexOf(uri.scheme) === -1) {
return undefined;
}
@@ -379,7 +380,7 @@ export class WebviewElement extends Disposable implements Webview {
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IEnvironmentService environmentService: IEnvironmentService,
@ITextFileService textFileService: ITextFileService,
@IFileService fileService: IFileService,
@ITunnelService tunnelService: ITunnelService,
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -422,7 +423,7 @@ export class WebviewElement extends Disposable implements Webview {
this._options.extension ? this._options.extension.location : undefined,
() => (this.content.options.localResourceRoots || []),
environmentService,
textFileService));
fileService));
this._register(new WebviewPortMappingProvider(
session,
@@ -7,17 +7,20 @@ import { extname, sep } from 'vs/base/common/path';
import { startsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import * as electron from 'electron';
type BufferProtocolCallback = (buffer?: Buffer | electron.MimeTypedBuffer | { error: number }) => void;
export const enum WebviewProtocol {
CoreResource = 'vscode-core-resource',
VsCodeResource = 'vscode-resource',
}
function resolveContent(textFileService: ITextFileService, resource: URI, mime: string, callback: any): void {
textFileService.read(resource, { encoding: 'binary' }).then(contents => {
function resolveContent(fileService: IFileService, resource: URI, mime: string, callback: BufferProtocolCallback): void {
fileService.readFile(resource).then(contents => {
callback({
data: Buffer.from(contents.value, contents.encoding),
data: Buffer.from(contents.value.buffer),
mimeType: mime
});
}, (err) => {
@@ -27,9 +30,9 @@ function resolveContent(textFileService: ITextFileService, resource: URI, mime:
}
export function registerFileProtocol(
contents: Electron.WebContents,
contents: electron.WebContents,
protocol: WebviewProtocol,
textFileService: ITextFileService,
fileService: IFileService,
extensionLocation: URI | undefined,
getRoots: () => ReadonlyArray<URI>
) {
@@ -51,10 +54,10 @@ export function registerFileProtocol(
requestResourcePath: requestUri.path
})
});
resolveContent(textFileService, redirectedUri, getMimeType(requestUri), callback);
resolveContent(fileService, redirectedUri, getMimeType(requestUri), callback);
return;
} else {
resolveContent(textFileService, normalizedPath, getMimeType(normalizedPath), callback);
resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback);
return;
}
}
@@ -44,6 +44,7 @@ export default () => `
<li><a href="command:workbench.action.openDocumentationUrl">${escape(localize('welcomePage.productDocumentation', "Product documentation"))}</a></li>
<li><a href="https://github.com/Microsoft/vscode">${escape(localize('welcomePage.gitHubRepository', "GitHub repository"))}</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/vscode?sort=votes&pageSize=50">${escape(localize('welcomePage.stackOverflow', "Stack Overflow"))}</a></li>
<li><a href="command:workbench.action.openNewsletterSignupUrl">${escape(localize('welcomePage.newsletterSignup', "Join our Newsletter"))}</a></li>
</ul>
</div>
<p class="showOnStartup"><input type="checkbox" id="showOnStartup" class="checkbox"> <label class="caption" for="showOnStartup">${escape(localize('welcomePage.showOnStartup', "Show welcome page on startup"))}</label></p>
@@ -7,6 +7,7 @@ import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import product from 'vs/platform/product/node/product';
import { isMacintosh, isLinux, language } from 'vs/base/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export class KeybindingsReferenceAction extends Action {
@@ -91,7 +92,31 @@ export class OpenTipsAndTricksUrlAction extends Action {
run(): Promise<void> {
window.open(OpenTipsAndTricksUrlAction.URL);
return Promise.resolve();
}
}
export class OpenNewsletterSignupUrlAction extends Action {
static readonly ID = 'workbench.action.openNewsletterSignupUrl';
static readonly LABEL = nls.localize('newsletterSignup', "Signup for the VS Code Newsletter");
private telemetryService: ITelemetryService;
private static readonly URL = product.newsletterSignupUrl;
static readonly AVAILABLE = !!OpenNewsletterSignupUrlAction.URL;
constructor(
id: string,
label: string,
@ITelemetryService telemetryService: ITelemetryService
) {
super(id, label);
this.telemetryService = telemetryService;
}
run(): Promise<void> {
this.telemetryService.getTelemetryInfo().then(info => {
window.open(`${OpenNewsletterSignupUrlAction.URL}?machineId=${encodeURIComponent(info.machineId)}`);
});
return Promise.resolve();
}
}
@@ -11,7 +11,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction } from 'vs/workbench/electron-browser/actions/helpActions';
import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenNewsletterSignupUrlAction } from 'vs/workbench/electron-browser/actions/helpActions';
import { ToggleSharedProcessAction, InspectContextKeysAction, ToggleScreencastModeAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions';
import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ReloadWindowAction, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions';
import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction, OpenLocalFileAction, OpenLocalFolderAction, OpenLocalFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
@@ -207,6 +207,10 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/electron-brow
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory);
}
if (OpenNewsletterSignupUrlAction.AVAILABLE) {
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory);
}
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory);
+2 -2
View File
@@ -23,7 +23,7 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { webFrame } from 'electron';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
import { StorageService } from 'vs/platform/storage/node/storageService';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
@@ -322,7 +322,7 @@ class CodeRendererMain extends Disposable {
}
private createLogService(mainProcessService: IMainProcessService, environmentService: IWorkbenchEnvironmentService): ILogService {
const spdlogService = createSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath);
const spdlogService = createBufferSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath);
const consoleLogService = new ConsoleLogService(this.configuration.logLevel);
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel'));
@@ -29,6 +29,7 @@ export class RemoteUserConfiguration extends Disposable {
private readonly _cachedConfiguration: CachedUserConfiguration;
private readonly _configurationFileService: IConfigurationFileService;
private _userConfiguration: UserConfiguration | CachedUserConfiguration;
private _userConfigurationInitializationPromise: Promise<ConfigurationModel> | null = null;
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
@@ -46,7 +47,8 @@ export class RemoteUserConfiguration extends Disposable {
if (environment) {
const userConfiguration = this._register(new UserConfiguration(environment.settingsPath, MACHINE_SCOPES, this._configurationFileService));
this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
const configurationModel = await userConfiguration.initialize();
this._userConfigurationInitializationPromise = userConfiguration.initialize();
const configurationModel = await this._userConfigurationInitializationPromise;
this._userConfiguration.dispose();
this._userConfiguration = userConfiguration;
this.onDidUserConfigurationChange(configurationModel);
@@ -54,8 +56,20 @@ export class RemoteUserConfiguration extends Disposable {
});
}
initialize(): Promise<ConfigurationModel> {
return this._userConfiguration.initialize();
async initialize(): Promise<ConfigurationModel> {
if (this._userConfiguration instanceof UserConfiguration) {
return this._userConfiguration.initialize();
}
// Initialize cached configuration
let configurationModel = await this._userConfiguration.initialize();
if (this._userConfigurationInitializationPromise) {
// Use user configuration
configurationModel = await this._userConfigurationInitializationPromise;
this._userConfigurationInitializationPromise = null;
}
return configurationModel;
}
reload(): Promise<ConfigurationModel> {
@@ -543,8 +543,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
if (this._configuration) {
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
}
}
private onWorkspaceConfigurationChanged(): Promise<void> {
@@ -22,7 +22,7 @@ import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configurati
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestTextFileService, RemoteFileSystemProvider } from 'vs/workbench/test/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -42,6 +42,8 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -95,6 +97,177 @@ suite('WorkspaceContextService - Folder', () => {
// {{SQL CARBON EDIT}} - Remove tests
assert.equal(0, 0);
});
test('configuration of newly added folder is available on configuration change event', async () => {
// {{SQL CARBON EDIT}} - Remove tests
assert.equal(0, 0);
});
});
suite('WorkspaceConfigurationService - Remote Folder', () => {
let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: WorkspaceService, globalSettingsFile: string, remoteSettingsFile: string, instantiationService: TestInstantiationService, resolveRemoteEnvironment: () => void;
const remoteAuthority = 'configuraiton-tests';
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
suiteSetup(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.remote.applicationSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
},
'configurationService.remote.machineSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
},
'configurationService.remote.testSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.RESOURCE
}
}
});
});
setup(() => {
return setUpFolderWorkspace(workspaceName)
.then(({ parentDir, folderDir }) => {
parentResource = parentDir;
workspaceDir = folderDir;
globalSettingsFile = path.join(parentDir, 'settings.json');
remoteSettingsFile = path.join(parentDir, 'remote-settings.json');
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
const remoteEnvironmentPromise = new Promise<Partial<IRemoteAgentEnvironment>>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) }));
const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<IRemoteAgentService>>{ getEnvironment: () => remoteEnvironmentPromise });
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
const configurationFileService = new ConfigurationFileService();
configurationFileService.fileService = fileService;
const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() };
testObject = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache, remoteAuthority }, configurationFileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
instantiationService.stub(IEnvironmentService, environmentService);
instantiationService.stub(IFileService, fileService);
});
});
async function initialize(): Promise<void> {
await testObject.initialize(convertToWorkspacePayload(URI.file(workspaceDir)));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
testObject.acquireInstantiationService(instantiationService);
}
function registerRemoteFileSystemProvider(): void {
instantiationService.get(IFileService).registerProvider(Schemas.vscodeRemote, new RemoteFileSystemProvider(diskFileSystemProvider, remoteAuthority));
}
function registerRemoteFileSystemProviderOnActivation(): void {
const disposable = instantiationService.get(IFileService).onWillActivateFileSystemProvider(e => {
if (e.scheme === Schemas.vscodeRemote) {
disposable.dispose();
e.join(Promise.resolve().then(() => registerRemoteFileSystemProvider()));
}
});
}
teardown(() => {
if (testObject) {
(<WorkspaceService>testObject).dispose();
}
if (parentResource) {
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
}
return undefined;
});
test('remote settings override globals', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProvider();
resolveRemoteEnvironment();
await initialize();
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
});
test('remote settings override globals after remote provider is registered on activation', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
resolveRemoteEnvironment();
registerRemoteFileSystemProviderOnActivation();
await initialize();
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
});
test('remote settings override globals after remote environment is resolved', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProvider();
await initialize();
const promise = new Promise((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
c();
} catch (error) {
e(error);
}
});
});
resolveRemoteEnvironment();
return promise;
});
test('remote settings override globals after remote provider is registered on activation and remote environment is resolved', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProviderOnActivation();
await initialize();
const promise = new Promise((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
c();
} catch (error) {
e(error);
}
});
});
resolveRemoteEnvironment();
return promise;
});
// test('update remote settings', async () => {
// registerRemoteFileSystemProvider();
// resolveRemoteEnvironment();
// await initialize();
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet');
// const promise = new Promise((c, e) => {
// testObject.onDidChangeConfiguration(event => {
// try {
// assert.equal(event.source, ConfigurationTarget.USER);
// assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
// c();
// } catch (error) {
// e(error);
// }
// });
// });
// fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
// return promise;
// });
});
function getWorkspaceId(configPath: URI): string {
@@ -279,9 +279,8 @@ export class RemoteFileDialog {
if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) {
this.filePickBox.validationMessage = undefined;
const filePickBoxUri = this.filePickBoxValue();
const valueUri = resources.removeTrailingPathSeparator(filePickBoxUri);
let updated: UpdateResult = UpdateResult.NotUpdated;
if (!resources.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), valueUri, true)) {
if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) {
updated = await this.tryUpdateItems(value, filePickBoxUri);
}
if (updated === UpdateResult.NotUpdated) {
@@ -660,7 +659,8 @@ export class RemoteFileDialog {
if (force && trailing) {
// Keep the cursor position in front of the save as name.
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length];
} else {
} else if (!trailing) {
// If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end.
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
}
this.filePickBox.busy = false;
@@ -16,6 +16,12 @@ import { URI } from 'vs/base/common/uri';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/product/node/package';
@@ -35,6 +41,7 @@ import { Schemas } from 'vs/base/common/network';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -64,6 +71,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
private _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
private readonly _extensionHostLogsLocation: URI;
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
@@ -102,6 +111,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IWindowService private readonly _windowService: IWindowService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IFileService fileService: IFileService
) {
@@ -112,7 +124,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}));
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`));
this._registry = new ExtensionDescriptionRegistry([]);
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
@@ -425,6 +439,17 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private _createProvider(remoteAuthority: string): IInitDataProvider {
return {
remoteAuthority: remoteAuthority,
getInitData: () => {
return this._installedExtensionsReady.wait().then(() => {
return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!;
});
}
};
}
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
@@ -436,52 +461,63 @@ export class ExtensionService extends Disposable implements IExtensionService {
} else {
// restart case
autoStart = true;
extensions = this.getExtensions();
extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file));
}
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(extHostProcessManager);
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority));
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false));
remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(remoteExtHostProcessManager);
}
}
private _onExtensionHostCrashed(code: number, signal: string | null): void {
private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHostProcess();
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
if (showNotification) {
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
}]
);
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
[{
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
}
// ---- begin IExtensionService
@@ -590,12 +626,112 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
}
private async _resolveAuthorityAgain(): Promise<void> {
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return;
}
const extensionHost = this._extensionHostProcessManagers[0];
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
try {
const resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
} catch (err) {
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
}
}
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(this.createLogger());
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const extensionHost = this._extensionHostProcessManagers[0];
const extensions = await this._extensionScanner.scannedExtensions;
const enabledExtensions = await this._getRuntimeExtensions(extensions);
let localExtensions = await this._extensionScanner.scannedExtensions;
if (remoteAuthority) {
let resolvedAuthority: ResolvedAuthority;
try {
resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
} catch (err) {
console.error(err);
const plusIndex = remoteAuthority.indexOf('+');
const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority;
if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) {
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) });
} else {
console.log(`Not showing a notification for the error`);
}
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
// Proceed with the local extension host
await this._startLocalExtensionHost(extensionHost, localExtensions);
return;
}
// set the resolved authority
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
// monitor for breakage
const connection = this._remoteAgentService.getConnection();
if (connection) {
connection.onDidStateChange(async (e) => {
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return;
}
if (e.type === PersistenConnectionEventType.ConnectionLost) {
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
}
});
connection.onReconnecting(() => this._resolveAuthorityAgain());
}
// fetch the remote environment
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
// revive URIs
remoteEnv.extensions.forEach((extension) => {
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
});
// remove UI extensions from the remote extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService));
// remove non-UI extensions from the local extensions
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService));
// in case of overlap, the remote wins
const isRemoteExtension = new Set<string>();
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
// compute enabled extensions
const enabledExtensions = await this._getRuntimeExtensions((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
// remove disabled extensions
const isEnabled = new Set<string>();
enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
// save for remote extension's init data
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(localExtensions.map(extension => extension.identifier));
this._releaseBarrier();
} else {
await this._startLocalExtensionHost(extensionHost, localExtensions);
}
}
private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise<void> {
const enabledExtensions = await this._getRuntimeExtensions(localExtensions);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
@@ -0,0 +1,247 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer as ipc } from 'electron';
import { Emitter, Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as platform from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { VSBuffer } from 'vs/base/common/buffer';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
export interface IInitDataProvider {
readonly remoteAuthority: string;
getInitData(): Promise<IRemoteAgentEnvironment>;
}
export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter {
private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event;
private _protocol: PersistentProtocol | null;
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevTestFromCli: boolean;
private _terminating: boolean;
constructor(
private readonly _allExtensions: Promise<IExtensionDescription[]>,
private readonly _initDataProvider: IInitDataProvider,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
) {
super();
this._protocol = null;
this._terminating = false;
this._register(this._lifecycleService.onShutdown(reason => this.dispose()));
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
}
public start(): Promise<IMessagePassingProtocol> {
const options: IConnectionOptions = {
isBuilt: this._environmentService.isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host, port };
}
}
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => {
const startParams: IRemoteExtensionHostStartParams = {
language: platform.language,
debugId: this._environmentService.debugExtensionHost.debugId,
break: this._environmentService.debugExtensionHost.break,
port: this._environmentService.debugExtensionHost.port,
};
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
let debugOk = true;
if (extDevLocs && extDevLocs.length > 0) {
// TODO@AW: handles only first path in array
if (extDevLocs[0].scheme === Schemas.file) {
debugOk = false;
}
}
if (!debugOk) {
startParams.break = false;
}
return connectRemoteAgentExtensionHost(options, startParams).then(result => {
let { protocol, debugPort } = result;
const isExtensionDevelopmentDebug = typeof debugPort === 'number';
if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
}
protocol.onClose(() => {
this._onExtHostConnectionLost();
});
protocol.onSocketClose(() => {
if (this._isExtensionDevHost) {
this._onExtHostConnectionLost();
}
});
// 1) wait for the incoming `ready` event and send the initialization data.
// 2) wait for the incoming `initialized` event.
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
let handle = setTimeout(() => {
reject('timeout');
}, 60 * 1000);
const disposable = protocol.onMessage(msg => {
if (isMessageOfType(msg, MessageType.Ready)) {
// 1) Extension Host is ready to receive messages, initialize it
this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => protocol.send(VSBuffer.fromString(JSON.stringify(data))));
return;
}
if (isMessageOfType(msg, MessageType.Initialized)) {
// 2) Extension Host is initialized
clearTimeout(handle);
// stop listening for messages here
disposable.dispose();
// release this promise
this._protocol = protocol;
resolve(protocol);
return;
}
console.error(`received unexpected message during handshake phase from the extension host: `, msg);
});
});
});
});
}
private _onExtHostConnectionLost(): void {
if (this._terminating) {
// Expected termination path (we asked the process to terminate)
return;
}
// Unexpected termination
if (!this._isExtensionDevHost) {
this._onCrashed.fire([0, null]);
}
// Expected development extension termination: When the extension host goes down we also shutdown the window
else if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
}
// When CLI testing make sure to exit with proper exit code
else {
ipc.send('vscode:exit', 0);
}
}
private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IInitData> {
return Promise.all([this._allExtensions, this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]).then(([allExtensions, telemetryInfo, remoteExtensionHostData]) => {
// Collect all identifiers for extension ids which can be considered "resolved"
const resolvedExtensions = allExtensions.filter(extension => !extension.main).map(extension => extension.identifier);
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
version: pkg.version,
parentPid: remoteExtensionHostData.pid,
environment: {
isExtensionDevelopmentDebug,
appRoot: remoteExtensionHostData.appRoot,
appSettingsHome: remoteExtensionHostData.appSettingsHome,
appName: product.nameLong,
appUriScheme: product.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: remoteExtensionHostData.globalStorageHome,
userHome: remoteExtensionHostData.userHome
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
resolvedExtensions: resolvedExtensions,
hostExtensions: hostExtensions,
extensions: remoteExtensionHostData.extensions,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: remoteExtensionHostData.extensionHostLogsPath,
autoStart: true,
remoteAuthority: this._initDataProvider.remoteAuthority,
};
return r;
});
}
getInspectPort(): number | undefined {
return undefined;
}
dispose(): void {
super.dispose();
this._terminating = true;
if (this._protocol) {
// Send the extension host a request to terminate itself
// (graceful termination)
const socket = this._protocol.getSocket();
this._protocol.send(createMessageOfType(MessageType.Terminate));
this._protocol.sendDisconnect();
this._protocol.dispose();
socket.end();
this._protocol = null;
}
}
}
@@ -15,7 +15,7 @@ import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHo
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
import { VSBuffer } from 'vs/base/common/buffer';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { IURITransformer } from 'vs/base/common/uriIpc';
@@ -72,7 +72,7 @@ function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
};
}
const createLogService: ILogServiceFn = initData => createSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
const createLogService: ILogServiceFn = initData => createBufferSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
interface IRendererConnection {
protocol: IMessagePassingProtocol;
@@ -18,6 +18,8 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { localize } from 'vs/nls';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
@@ -145,7 +147,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix);
// Install UI Dependencies on local server
await this.installUIDependencies(manifest);
await this.installUIDependenciesAndPackedExtensions(manifest);
return promise;
}
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
@@ -165,8 +167,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte
}
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
// Install UI Dependencies on local server
await this.installUIDependencies(manifest);
// Install UI dependencies and packed extensions on local server
await this.installUIDependenciesAndPackedExtensions(manifest);
return promise;
} else {
return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
@@ -175,18 +177,11 @@ export class MultiExtensionManagementService extends Disposable implements IExte
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
private async installUIDependencies(manifest: IExtensionManifest): Promise<void> {
if (manifest.extensionDependencies && manifest.extensionDependencies.length) {
const dependencies = await this.extensionGalleryService.loadAllDependencies(manifest.extensionDependencies.map(id => ({ id })), CancellationToken.None);
if (dependencies.length) {
await Promise.all(dependencies.map(async d => {
const manifest = await this.extensionGalleryService.getManifest(d, CancellationToken.None);
if (manifest && isUIExtension(manifest, this.configurationService)) {
await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d);
}
}));
}
}
private async installUIDependenciesAndPackedExtensions(manifest: IExtensionManifest): Promise<void> {
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(manifest, CancellationToken.None);
const installed = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getInstalled();
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
await Promise.all(toInstall.map(d => this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d)));
}
getExtensionsReport(): Promise<IReportedExtension[]> {
@@ -196,6 +191,49 @@ export class MultiExtensionManagementService extends Disposable implements IExte
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getAllUIDependenciesAndPackedExtensionsRecursively(extensions, result, token);
return values(result);
}
private async getAllUIDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, token: CancellationToken): Promise<void> {
if (toGet.length === 0) {
return Promise.resolve();
}
const extensions = (await this.extensionGalleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage;
const manifests = await Promise.all(extensions.map(e => this.extensionGalleryService.getManifest(e, token)));
const uiExtensionsManifests: IExtensionManifest[] = [];
for (let idx = 0; idx < extensions.length; idx++) {
const extension = extensions[idx];
const manifest = manifests[idx];
if (manifest && isUIExtension(manifest, this.configurationService)) {
result.set(extension.identifier.id.toLowerCase(), extension);
uiExtensionsManifests.push(manifest);
}
}
toGet = [];
for (const uiExtensionManifest of uiExtensionsManifests) {
if (isNonEmptyArray(uiExtensionManifest.extensionDependencies)) {
for (const id of uiExtensionManifest.extensionDependencies) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
if (isNonEmptyArray(uiExtensionManifest.extensionPack)) {
for (const id of uiExtensionManifest.extensionPack) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
}
return this.getAllUIDependenciesAndPackedExtensionsRecursively(toGet, result, token);
}
}
registerSingleton(IExtensionManagementService, MultiExtensionManagementService);
@@ -6,20 +6,25 @@
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { IProductService } from 'vs/platform/product/common/product';
import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory';
export class RemoteAgentService extends AbstractRemoteAgentService {
private readonly _connection: IRemoteAgentConnection | null = null;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IProductService productService: IProductService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super(environmentService);
const authority = document.location.host;
this._connection = this._register(new RemoteAgentConnection(authority, productService.commit, browserWebSocketFactory, environmentService, remoteAuthorityResolverService));
}
getConnection(): IRemoteAgentConnection | null {
return null;
return this._connection;
}
}
@@ -3,16 +3,111 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as net from 'net';
import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import product from 'vs/platform/product/node/product';
import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort);
return tunnel.waitForReady();
}
class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public readonly tunnelRemotePort: number;
public readonly tunnelLocalPort: number;
private readonly _options: IConnectionOptions;
private readonly _server: net.Server;
private readonly _barrier: Barrier;
private readonly _listeningListener: () => void;
private readonly _connectionListener: (socket: net.Socket) => void;
constructor(options: IConnectionOptions, tunnelRemotePort: number) {
super();
this._options = options;
this._server = net.createServer();
this._barrier = new Barrier();
this._listeningListener = () => this._barrier.open();
this._server.on('listening', this._listeningListener);
this._connectionListener = (socket) => this._onConnection(socket);
this._server.on('connection', this._connectionListener);
this.tunnelRemotePort = tunnelRemotePort;
this.tunnelLocalPort = (<net.AddressInfo>this._server.listen(0).address()).port;
}
public dispose(): void {
super.dispose();
this._server.removeListener('listening', this._listeningListener);
this._server.removeListener('connection', this._connectionListener);
this._server.close();
}
public async waitForReady(): Promise<this> {
await this._barrier.wait();
return this;
}
private async _onConnection(localSocket: net.Socket): Promise<void> {
// pause reading on the socket until we have a chance to forward its data
localSocket.pause();
const protocol = await connectRemoteAgentTunnel(this._options, this.tunnelRemotePort);
const remoteSocket = (<NodeSocket>protocol.getSocket()).socket;
const dataChunk = protocol.readEntireBuffer();
protocol.dispose();
if (dataChunk.byteLength > 0) {
localSocket.write(dataChunk.buffer);
}
localSocket.on('end', () => remoteSocket.end());
localSocket.on('close', () => remoteSocket.end());
remoteSocket.on('end', () => localSocket.end());
remoteSocket.on('close', () => localSocket.end());
localSocket.pipe(remoteSocket);
remoteSocket.pipe(localSocket);
}
}
export class TunnelService implements ITunnelService {
_serviceBrand: any;
public constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
}
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
const options: IConnectionOptions = {
isBuilt: this.environmentService.isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
return { host, port };
}
}
};
return createRemoteTunnel(options, remotePort);
}
}
@@ -312,7 +312,7 @@ export class SearchService implements IRawSearchService {
// Pattern match on results
const results: IRawFileMatch[] = [];
const normalizedSearchValueLowercase = prepareQuery(searchValue).value;
const normalizedSearchValueLowercase = prepareQuery(searchValue).lowercase;
for (const entry of cachedEntries) {
// Check if this entry is a match for the search value
@@ -83,6 +83,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
import { Schemas } from 'vs/base/common/network';
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
@@ -1598,3 +1599,32 @@ export class NullFileSystemProvider implements IFileSystemProvider {
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return Promise.resolve(undefined!); }
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return Promise.resolve(undefined!); }
}
export class RemoteFileSystemProvider implements IFileSystemProvider {
constructor(private readonly diskFileSystemProvider: IFileSystemProvider, private readonly remoteAuthority: string) { }
readonly capabilities: FileSystemProviderCapabilities = this.diskFileSystemProvider.capabilities;
readonly onDidChangeCapabilities: Event<void> = this.diskFileSystemProvider.onDidChangeCapabilities;
readonly onDidChangeFile: Event<IFileChange[]> = Event.map(this.diskFileSystemProvider.onDidChangeFile, changes => changes.map(c => { c.resource = c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }); return c; }));
watch(resource: URI, opts: IWatchOptions): IDisposable { return this.diskFileSystemProvider.watch(this.toFileResource(resource), opts); }
stat(resource: URI): Promise<IStat> { return this.diskFileSystemProvider.stat(this.toFileResource(resource)); }
mkdir(resource: URI): Promise<void> { return this.diskFileSystemProvider.mkdir(this.toFileResource(resource)); }
readdir(resource: URI): Promise<[string, FileType][]> { return this.diskFileSystemProvider.readdir(this.toFileResource(resource)); }
delete(resource: URI, opts: FileDeleteOptions): Promise<void> { return this.diskFileSystemProvider.delete(this.toFileResource(resource), opts); }
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return this.diskFileSystemProvider.rename(this.toFileResource(from), this.toFileResource(to), opts); }
copy(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return this.diskFileSystemProvider.copy!(this.toFileResource(from), this.toFileResource(to), opts); }
readFile(resource: URI): Promise<Uint8Array> { return this.diskFileSystemProvider.readFile!(this.toFileResource(resource)); }
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> { return this.diskFileSystemProvider.writeFile!(this.toFileResource(resource), content, opts); }
open(resource: URI, opts: FileOpenOptions): Promise<number> { return this.diskFileSystemProvider.open!(this.toFileResource(resource), opts); }
close(fd: number): Promise<void> { return this.diskFileSystemProvider.close!(fd); }
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return this.diskFileSystemProvider.read!(fd, pos, data, offset, length); }
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return this.diskFileSystemProvider.write!(fd, pos, data, offset, length); }
private toFileResource(resource: URI): URI { return resource.with({ scheme: Schemas.file, authority: '' }); }
}
+4 -2
View File
@@ -372,9 +372,11 @@ import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution
// Tasks
import 'vs/workbench/contrib/tasks/electron-browser/task.contribution';
// {{SQL CARBON EDIT}}
// Remote
import 'vs/workbench/contrib/remote/electron-browser/remote.contribution';
// Emmet
// import 'vs/workbench/contrib/emmet/browser/emmet.contribution';
// import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; {{SQL CARBON EDIT}} @anthonydresser comment our emmet
// CodeEditor Contributions
import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution';