diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 427c9ee620..0b69def737 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -3,12 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); -import * as vscode from 'vscode'; -import { getLocation, visit, parse, ParseErrorCode } from 'jsonc-parser'; +import { getLocation, parse, visit } from 'jsonc-parser'; import * as path from 'path'; +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; +const localize = nls.loadMessageBundle(); const fadedDecoration = vscode.window.createTextEditorDecorationType({ light: { @@ -45,50 +45,6 @@ export function activate(context: vscode.ExtensionContext): void { } }, null, context.subscriptions)); updateLaunchJsonDecorations(vscode.window.activeTextEditor); - - context.subscriptions.push(vscode.workspace.onWillSaveTextDocument(e => { - if (!e.document.fileName.endsWith('/settings.json')) { - return; - } - - autoFixSettingsJSON(e); - })); -} - -function autoFixSettingsJSON(willSaveEvent: vscode.TextDocumentWillSaveEvent): void { - const document = willSaveEvent.document; - const text = document.getText(); - const edit = new vscode.WorkspaceEdit(); - - let lastEndOfSomething = -1; - visit(text, { - onArrayEnd(offset: number, length: number): void { - lastEndOfSomething = offset + length; - }, - - onLiteralValue(_value: any, offset: number, length: number): void { - lastEndOfSomething = offset + length; - }, - - onObjectEnd(offset: number, length: number): void { - lastEndOfSomething = offset + length; - }, - - onError(error: ParseErrorCode, _offset: number, _length: number): void { - if (error === ParseErrorCode.CommaExpected && lastEndOfSomething > -1) { - const fixPosition = document.positionAt(lastEndOfSomething); - - // Don't insert a comma immediately before a : or ' :' - const colonRange = document.getWordRangeAtPosition(fixPosition, / *:/); - if (!colonRange) { - edit.insert(document.uri, fixPosition, ','); - } - } - } - }); - - willSaveEvent.waitUntil( - vscode.workspace.applyEdit(edit)); } function registerSettingsCompletions(): vscode.Disposable { diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json index 66490a1ec9..139ae46369 100644 --- a/extensions/python/cgmanifest.json +++ b/extensions/python/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "MagicStack/MagicPython", "repositoryUrl": "https://github.com/MagicStack/MagicPython", - "commitHash": "8ff35b3e5fcde471fae62a57ea1ae1c7cd34c9fc" + "commitHash": "38422d302fe0b3e7716d26ce8cd7d0b9685f3a38" } }, "license": "MIT", diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json index 25c572d4be..ca3617c466 100644 --- a/extensions/python/syntaxes/MagicPython.tmLanguage.json +++ b/extensions/python/syntaxes/MagicPython.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/MagicStack/MagicPython/commit/8ff35b3e5fcde471fae62a57ea1ae1c7cd34c9fc", + "version": "https://github.com/MagicStack/MagicPython/commit/38422d302fe0b3e7716d26ce8cd7d0b9685f3a38", "name": "MagicPython", "scopeName": "source.python", "patterns": [ @@ -826,7 +826,7 @@ ] }, "f-expression": { - "comment": "All valid Python expressions, except comments and line cont", + "comment": "All valid Python expressions, except comments and line continuation", "patterns": [ { "include": "#expression-bare" diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 61cd8f69e5..a36f511335 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -30,6 +30,16 @@ exports.load = function (modulePaths, resultCallback, options) { const path = require('path'); const args = parseURLQueryArgs(); + /** + * // configuration: IWindowConfiguration + * @type {{ + * zoomLevel?: number, + * extensionDevelopmentPath?: string | string[], + * extensionTestsPath?: string, + * userEnv?: { [key: string]: string | undefined }, + * appRoot?: string, + * nodeCachedDataDir?: string + * }} */ const configuration = JSON.parse(args['config'] || '{}') || {}; // Apply zoom level early to avoid glitches diff --git a/src/sql/parts/dashboard/contents/webviewContent.component.ts b/src/sql/parts/dashboard/contents/webviewContent.component.ts index 1fc4fa47d4..226e4c6df1 100644 --- a/src/sql/parts/dashboard/contents/webviewContent.component.ts +++ b/src/sql/parts/dashboard/contents/webviewContent.component.ts @@ -107,7 +107,6 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo } this._webview = this.instantiationService.createInstance(WebviewElement, - this.layoutService.getContainer(Parts.EDITOR_PART), {}, { allowScripts: true diff --git a/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts b/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts index 3efb5b3a60..a4312604d3 100644 --- a/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts +++ b/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts @@ -104,7 +104,6 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, } this._webview = this.instantiationService.createInstance(WebviewElement, - this.layoutService.getContainer(Parts.EDITOR_PART), {}, { allowScripts: true, diff --git a/src/sql/parts/modelComponents/webview.component.ts b/src/sql/parts/modelComponents/webview.component.ts index dffcc815a0..c346f870a6 100644 --- a/src/sql/parts/modelComponents/webview.component.ts +++ b/src/sql/parts/modelComponents/webview.component.ts @@ -77,7 +77,6 @@ export default class WebViewComponent extends ComponentBase implements IComponen private _createWebview(): void { this._webview = this.instantiationService.createInstance(WebviewElement, - this.layoutService.getContainer(Parts.EDITOR_PART), { allowSvgs: true }, diff --git a/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts b/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts index 25387c5164..813377eb7d 100644 --- a/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts +++ b/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts @@ -88,7 +88,6 @@ export class WebViewDialog extends Modal { this._body = DOM.append(container, DOM.$('div.webview-dialog')); this._webview = this._instantiationService.createInstance(WebviewElement, - this.layoutService.getContainer(Parts.EDITOR_PART), {}, { allowScripts: true diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 3dabdfb972..57fd3a5d4a 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -274,7 +274,7 @@ export class BreadcrumbsWidget { this._onDidSelectItem.fire({ type: 'select', item: this._items[this._selectedItemIdx], node: this._nodes[this._selectedItemIdx], payload }); } - getItems(): ReadonlyArray { + getItems(): readonly BreadcrumbsItem[] { return this._items; } diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index eaead09abb..b8fe94b85c 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -5,10 +5,10 @@ import { URI } from 'vs/base/common/uri'; import { sep, posix, normalize } from 'vs/base/common/path'; -import { endsWith, ltrim, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; +import { endsWith, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; -import { isEqual, basename } from 'vs/base/common/resources'; +import { isEqual, basename, relativePath } from 'vs/base/common/resources'; import { CharCode } from 'vs/base/common/charCode'; export interface IWorkspaceFolderProvider { @@ -40,8 +40,7 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom if (isEqual(baseResource.uri, resource)) { pathLabel = ''; // no label if paths are identical } else { - // TODO: isidor use resources.relative - pathLabel = normalize(ltrim(resource.path.substr(baseResource.uri.path.length), posix.sep)!); + pathLabel = relativePath(baseResource.uri, resource)!; } if (hasMultipleRoots) { diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index ded8df0ffe..8d19680205 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -917,6 +917,18 @@ export class IssueReporter extends Disposable { VM${systemInfo.vmHint} `; + systemInfo.remoteData.forEach(remote => { + renderedData += ` +
+ + + + + + +
Remote${remote.hostName}
OS${remote.machineInfo.os}
CPUs${remote.machineInfo.cpus}
Memory (System)${remote.machineInfo.memory}
VM${remote.machineInfo.vmHint}
`; + }); + target.innerHTML = renderedData; } } diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index c12fb1beb2..4758ebbed4 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -69,11 +69,19 @@ ${this._data.issueDescription} Azure Data Studio version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion} OS version: ${this._data.versionInfo && this._data.versionInfo.os} - +${this.getRemoteOSes()} ${this.getInfos()} `; } + private getRemoteOSes(): string { + if (this._data.systemInfo && this._data.systemInfo.remoteData.length) { + return this._data.systemInfo.remoteData.map(remote => `Remote OS version: ${remote.machineInfo.os}`).join('\n') + '\n'; + } + + return ''; + } + fileOnExtension(): boolean | undefined { const fileOnExtensionSupported = this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue @@ -159,6 +167,18 @@ ${this.getInfos()} |Process Argv|${this._data.systemInfo.processArgs}| |Screen Reader|${this._data.systemInfo.screenReader}| |VM|${this._data.systemInfo.vmHint}|`; + + this._data.systemInfo.remoteData.forEach(remote => { + md += ` + +|Item|Value| +|---|---| +|Remote|${remote.hostName}| +|OS|${remote.machineInfo.os}| +|CPUs|${remote.machineInfo.cpus}| +|Memory (System)|${remote.machineInfo.memory}| +|VM|${remote.machineInfo.vmHint}|`; + }); } md += '\n'; diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts index dd996606af..2ffcb07476 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts @@ -50,6 +50,7 @@ Extensions: none vmHint: '0%', processArgs: '', screenReader: 'no', + remoteData: [], gpuStatus: { '2d_canvas': 'enabled', 'checker_imaging': 'disabled_off' @@ -82,6 +83,67 @@ OS version: undefined `); }); + test('serializes remote information when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '', + screenReader: 'no', + gpuStatus: { + '2d_canvas': 'enabled', + 'checker_imaging': 'disabled_off' + }, + remoteData: [ + { + hostName: 'SSH: Pineapple', + machineInfo: { + os: 'Linux x64 4.18.0', + cpus: 'Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz (2 x 2294)', + memory: '8GB', + vmHint: '100%' + } + } + ] + } + }); + assert.equal(issueReporterModel.serialize(), + ` +Issue Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Remote OS version: Linux x64 4.18.0 + +
+System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|2d_canvas: enabled
checker_imaging: disabled_off| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|| +|Screen Reader|no| +|VM|0%| + +|Item|Value| +|---|---| +|Remote|SSH: Pineapple| +|OS|Linux x64 4.18.0| +|CPUs|Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz (2 x 2294)| +|Memory (System)|8GB| +|VM|100%| +
Extensions: none +`); + }); + test('should normalize GitHub urls', () => { [ 'https://github.com/repo', diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 49b43ba3fc..b92d36c696 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -64,7 +64,14 @@ bootstrapWindow.load([ }); /** - * @param {object} configuration + * // configuration: IWindowConfiguration + * @param {{ + * partsSplashPath?: string, + * highContrast?: boolean, + * extensionDevelopmentPath?: string | string[], + * folderUri?: object, + * workspace?: object + * }} configuration */ function showPartsSplash(configuration) { perf.mark('willShowPartsSplash'); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index daecb89464..4d0ca9a71f 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -30,7 +30,7 @@ import { endsWith } from 'vs/base/common/strings'; export interface IWindowCreationOptions { state: IWindowState; - extensionDevelopmentPath?: string; + extensionDevelopmentPath?: string | string[]; isExtensionTestHost?: boolean; } @@ -220,9 +220,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { return !!this.config.extensionTestsPath; } - get extensionDevelopmentPath(): string | undefined { + /* + get extensionDevelopmentPaths(): string | string[] | undefined { return this.config.extensionDevelopmentPath; } + */ get config(): IWindowConfiguration { return this.currentConfig; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 689c15703b..d446daf912 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -26,7 +26,7 @@ import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/ import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService, IRecent } from 'vs/platform/history/common/history'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; @@ -899,7 +899,7 @@ export class WindowsManager implements IWindowsMainService { for (let f of fileUris) { const fileUri = this.argToUri(f); if (fileUri) { - const path = this.parseUri({ fileUri }, parseOptions); + const path = this.parseUri(hasWorkspaceFileExtension(f) ? { workspaceUri: fileUri } : { fileUri }, parseOptions); if (path) { pathsToOpen.push(path); } @@ -1153,7 +1153,7 @@ export class WindowsManager implements IWindowsMainService { return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow }; } - openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void { + openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string | string[], openConfig: IOpenConfiguration): void { // Reload an existing extension development host window on the same path // We currently do not allow more than one extension development window @@ -1207,9 +1207,30 @@ export class WindowsManager implements IWindowsMainService { openConfig.cli['folder-uri'] = folderUris; openConfig.cli['file-uri'] = fileUris; - const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/); - if (match) { - openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority; + if (Array.isArray(extensionDevelopmentPath)) { + let authority: string | undefined = undefined; + for (let p of extensionDevelopmentPath) { + const match = p.match(/^vscode-remote:\/\/([^\/]+)/); + if (match) { + const auth = URI.parse(p).authority; + if (authority) { + if (auth !== authority) { + console.log('more than one authority'); + } + } else { + authority = auth; + } + } + } + if (authority) { + openConfig.cli['remote'] = authority; + } + + } else { + const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/); + if (match) { + openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority; + } } // Open it diff --git a/src/vs/code/node/windowsFinder.ts b/src/vs/code/node/windowsFinder.ts index 8a0bb30f95..a17433b363 100644 --- a/src/vs/code/node/windowsFinder.ts +++ b/src/vs/code/node/windowsFinder.ts @@ -14,7 +14,7 @@ export interface ISimpleWindow { openedWorkspace?: IWorkspaceIdentifier; openedFolderUri?: URI; - extensionDevelopmentPath?: string; + extensionDevelopmentPath?: string | string[]; lastFocusTime: number; } @@ -95,13 +95,33 @@ export function findWindowOnWorkspace(windows: W[], wor return null; } -export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPath: string): W | null { +export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPath: string | string[]): W | null { + + const matches = (uriString: string): boolean => { + if (Array.isArray(extensionDevelopmentPath)) { + return extensionDevelopmentPath.some(p => extpath.isEqual(p, uriString, !platform.isLinux /* ignorecase */)); + } else if (extensionDevelopmentPath) { + return extpath.isEqual(extensionDevelopmentPath, uriString, !platform.isLinux /* ignorecase */); + } + return false; + }; + for (const window of windows) { - // match on extension development path. The path can be a path or uri string, using paths.isEqual is not 100% correct but good enough - if (window.extensionDevelopmentPath && extpath.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) { - return window; + // match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough + + if (window.extensionDevelopmentPath) { + if (Array.isArray(window.extensionDevelopmentPath)) { + if (window.extensionDevelopmentPath.some(p => matches(p))) { + return window; + } + } else if (window.extensionDevelopmentPath) { + if (matches(window.extensionDevelopmentPath)) { + return window; + } + } } } + return null; } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 1c8c8e75fd..a36360468c 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1001,11 +1001,16 @@ export interface ILink { range: IRange; url?: URI | string; } + +export interface ILinksList { + links: ILink[]; + dispose?(): void; +} /** * A provider of links. */ export interface LinkProvider { - provideLinks(model: model.ITextModel, token: CancellationToken): ProviderResult; + provideLinks(model: model.ITextModel, token: CancellationToken): ProviderResult; resolveLink?: (link: ILink, token: CancellationToken) => ProviderResult; } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index e7ed3ce7b4..21402c1481 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -58,12 +58,14 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker this._workerManager = this._register(new WorkerManager(this._modelService)); // todo@joh make sure this happens only once - this._register(modes.LinkProviderRegistry.register('*', { + this._register(modes.LinkProviderRegistry.register('*', { provideLinks: (model, token) => { if (!canSyncModel(this._modelService, model.uri)) { - return Promise.resolve([]); // File too large + return Promise.resolve({ links: [] }); // File too large } - return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)); + return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => { + return links && { links }; + }); } })); this._register(modes.CompletionProviderRegistry.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService))); diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index a3e57504f3..64953ed807 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -32,7 +32,7 @@ export class CodeActionSet { } } - public readonly actions: ReadonlyArray; + public readonly actions: readonly CodeAction[]; public constructor(actions: CodeAction[]) { this.actions = mergeSort(actions, CodeActionSet.codeActionsComparator); diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index f07bbe94f7..b8d0ea3b42 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -8,9 +8,11 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { ILink, LinkProvider, LinkProviderRegistry } from 'vs/editor/common/modes'; +import { ILink, LinkProvider, LinkProviderRegistry, ILinksList } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { isDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { coalesce } from 'vs/base/common/arrays'; export class Link implements ILink { @@ -66,77 +68,99 @@ export class Link implements ILink { } } -export function getLinks(model: ITextModel, token: CancellationToken): Promise { +export class LinksList extends Disposable { - let links: Link[] = []; + readonly links: Link[]; + + constructor(tuples: [ILinksList, LinkProvider][]) { + super(); + let links: Link[] = []; + for (const [list, provider] of tuples) { + // merge all links + const newLinks = list.links.map(link => new Link(link, provider)); + links = LinksList._union(links, newLinks); + // register disposables + if (isDisposable(provider)) { + this._register(provider); + } + } + this.links = links; + } + + private static _union(oldLinks: Link[], newLinks: Link[]): Link[] { + // reunite oldLinks with newLinks and remove duplicates + let result: Link[] = []; + let oldIndex: number; + let oldLen: number; + let newIndex: number; + let newLen: number; + + for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) { + const oldLink = oldLinks[oldIndex]; + const newLink = newLinks[newIndex]; + + if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) { + // Remove the oldLink + oldIndex++; + continue; + } + + const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range); + + if (comparisonResult < 0) { + // oldLink is before + result.push(oldLink); + oldIndex++; + } else { + // newLink is before + result.push(newLink); + newIndex++; + } + } + + for (; oldIndex < oldLen; oldIndex++) { + result.push(oldLinks[oldIndex]); + } + for (; newIndex < newLen; newIndex++) { + result.push(newLinks[newIndex]); + } + + return result; + } + +} + +export function getLinks(model: ITextModel, token: CancellationToken): Promise { + + const lists: [ILinksList, LinkProvider][] = []; // ask all providers for links in parallel - const promises = LinkProviderRegistry.ordered(model).reverse().map(provider => { + const promises = LinkProviderRegistry.ordered(model).reverse().map((provider, i) => { return Promise.resolve(provider.provideLinks(model, token)).then(result => { - if (Array.isArray(result)) { - const newLinks = result.map(link => new Link(link, provider)); - links = union(links, newLinks); + if (result) { + lists[i] = [result, provider]; } }, onUnexpectedExternalError); }); - return Promise.all(promises).then(() => { - return links; - }); + return Promise.all(promises).then(() => new LinksList(coalesce(lists))); } -function union(oldLinks: Link[], newLinks: Link[]): Link[] { - // reunite oldLinks with newLinks and remove duplicates - let result: Link[] = []; - let oldIndex: number; - let oldLen: number; - let newIndex: number; - let newLen: number; - - for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) { - const oldLink = oldLinks[oldIndex]; - const newLink = newLinks[newIndex]; - - if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) { - // Remove the oldLink - oldIndex++; - continue; - } - - const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range); - - if (comparisonResult < 0) { - // oldLink is before - result.push(oldLink); - oldIndex++; - } else { - // newLink is before - result.push(newLink); - newIndex++; - } - } - - for (; oldIndex < oldLen; oldIndex++) { - result.push(oldLinks[oldIndex]); - } - for (; newIndex < newLen; newIndex++) { - result.push(newLinks[newIndex]); - } - - return result; -} - -CommandsRegistry.registerCommand('_executeLinkProvider', (accessor, ...args) => { +CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...args): Promise => { const [uri] = args; if (!(uri instanceof URI)) { - return undefined; + return []; } - const model = accessor.get(IModelService).getModel(uri); if (!model) { - return undefined; + return []; } - - return getLinks(model, CancellationToken.None); + const list = await getLinks(model, CancellationToken.None); + if (!list) { + return []; + } + const result = list.links.slice(0); + list.dispose(); + return result; }); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index e6b5b3db7d..a7828fee15 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -19,7 +19,7 @@ import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeSti import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { LinkProviderRegistry } from 'vs/editor/common/modes'; import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; -import { Link, getLinks } from 'vs/editor/contrib/links/getLinks'; +import { Link, getLinks, LinksList } from 'vs/editor/contrib/links/getLinks'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -157,7 +157,8 @@ class LinkDetector implements editorCommon.IEditorContribution { private enabled: boolean; private listenersToRemove: IDisposable[]; private readonly timeout: async.TimeoutTimer; - private computePromise: async.CancelablePromise | null; + private computePromise: async.CancelablePromise | null; + private activeLinksList: LinksList | null; private activeLinkDecorationId: string | null; private readonly openerService: IOpenerService; private readonly notificationService: INotificationService; @@ -210,6 +211,7 @@ class LinkDetector implements editorCommon.IEditorContribution { this.timeout = new async.TimeoutTimer(); this.computePromise = null; + this.activeLinksList = null; this.currentOccurrences = {}; this.activeLinkDecorationId = null; this.beginCompute(); @@ -246,10 +248,15 @@ class LinkDetector implements editorCommon.IEditorContribution { return; } + if (this.activeLinksList) { + this.activeLinksList.dispose(); + this.activeLinksList = null; + } + this.computePromise = async.createCancelablePromise(token => getLinks(model, token)); try { - const links = await this.computePromise; - this.updateDecorations(links); + this.activeLinksList = await this.computePromise; + this.updateDecorations(this.activeLinksList.links); } catch (err) { onUnexpectedError(err); } finally { @@ -380,6 +387,9 @@ class LinkDetector implements editorCommon.IEditorContribution { private stop(): void { this.timeout.cancel(); + if (this.activeLinksList) { + this.activeLinksList.dispose(); + } if (this.computePromise) { this.computePromise.cancel(); this.computePromise = null; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 0c66688d94..bf9b483893 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5253,11 +5253,16 @@ declare namespace monaco.languages { url?: Uri | string; } + export interface ILinksList { + links: ILink[]; + dispose?(): void; + } + /** * A provider of links. */ export interface LinkProvider { - provideLinks(model: editor.ITextModel, token: CancellationToken): ProviderResult; + provideLinks(model: editor.ITextModel, token: CancellationToken): ProviderResult; resolveLink?: (link: ILink, token: CancellationToken) => ProviderResult; } diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 0d71cbe5ce..082145af6d 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -125,7 +125,6 @@ export interface IConfigurationData { user: IConfigurationModel; workspace: IConfigurationModel; folders: { [folder: string]: IConfigurationModel }; - isComplete: boolean; } export function compare(from: IConfigurationModel, to: IConfigurationModel): { added: string[], removed: string[], updated: string[] } { diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 12c68325ee..cb1b65084a 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -513,8 +513,7 @@ export class Configuration { const { contents, overrides, keys } = this._folderConfigurations.get(folder)!; result[folder.toString()] = { contents, overrides, keys }; return result; - }, Object.create({})), - isComplete: true + }, Object.create({})) }; } diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnosticsService.ts index 61a98de5fa..bf5fa38f38 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnosticsService.ts @@ -22,9 +22,14 @@ export interface SystemInfo extends IMachineInfo { processArgs: string; gpuStatus: any; screenReader: string; + remoteData: IRemoteDiagnosticInfo[]; load?: string; } +export interface IRemoteDiagnosticInfo extends IDiagnosticInfo { + hostName: string; +} + export interface IDiagnosticInfoOptions { includeProcesses?: boolean; folders?: UriComponents[]; diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts index 647bab721c..56c33a2288 100644 --- a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts @@ -15,7 +15,7 @@ import { app } from 'electron'; import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { WorkspaceStats, SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IMachineInfo, WorkspaceStats, SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; import { collectWorkspaceStats, getMachineInfo } from 'vs/platform/diagnostics/node/diagnosticsService'; import { ProcessItem } from 'vs/base/common/processes'; @@ -46,12 +46,21 @@ export interface PerformanceInfo { processInfo?: string; workspaceInfo?: string; } - export class DiagnosticsService implements IDiagnosticsService { _serviceBrand: any; - formatEnvironment(info: IMainProcessInfo): string { + private formatMachineInfo(info: IMachineInfo): string { + const output: string[] = []; + output.push(`OS Version: ${info.os}`); + output.push(`CPUs: ${info.cpus}`); + output.push(`Memory (System): ${info.memory}`); + output.push(`VM: ${info.vmHint}`); + + return output.join('\n'); + } + + private formatEnvironment(info: IMainProcessInfo): string { const MB = 1024 * 1024; const GB = 1024 * MB; @@ -76,10 +85,40 @@ export class DiagnosticsService implements IDiagnosticsService { async getPerformanceInfo(launchService: ILaunchService): Promise { const info = await launchService.getMainProcessInfo(); - return Promise.all([listProcesses(info.mainPID), this.formatWorkspaceMetadata(info)]).then(result => { - const [rootProcess, workspaceInfo] = result; + return Promise.all([listProcesses(info.mainPID), this.formatWorkspaceMetadata(info)]).then(async result => { + let [rootProcess, workspaceInfo] = result; + let processInfo = this.formatProcessList(info, rootProcess); + + try { + const remoteData = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); + remoteData.forEach(diagnostics => { + processInfo += `\n\nRemote: ${diagnostics.hostName}`; + if (diagnostics.processes) { + processInfo += `\n${this.formatProcessList(info, diagnostics.processes)}`; + } + + if (diagnostics.workspaceMetadata) { + workspaceInfo += `\n| Remote: ${diagnostics.hostName}`; + for (const folder of Object.keys(diagnostics.workspaceMetadata)) { + const metadata = diagnostics.workspaceMetadata[folder]; + + let countMessage = `${metadata.fileCount} files`; + if (metadata.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + + workspaceInfo += `| Folder (${folder}): ${countMessage}`; + workspaceInfo += this.formatWorkspaceStats(metadata); + } + } + }); + } catch (e) { + processInfo += `\nFetching remote data failed: ${e}`; + workspaceInfo += `\nFetching remote data failed: ${e}`; + } + return { - processInfo: this.formatProcessList(info, rootProcess), + processInfo, workspaceInfo }; }); @@ -95,7 +134,8 @@ export class DiagnosticsService implements IDiagnosticsService { vmHint, processArgs: `${info.mainArguments.join(' ')}`, gpuStatus: app.getGPUFeatureStatus(), - screenReader: `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}` + screenReader: `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`, + remoteData: await launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false }) }; @@ -120,12 +160,42 @@ export class DiagnosticsService implements IDiagnosticsService { output.push(this.formatProcessList(info, rootProcess)); // Workspace Stats - if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) { + if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0 && !window.remoteAuthority)) { output.push(''); output.push('Workspace Stats: '); output.push(await this.formatWorkspaceMetadata(info)); } + try { + const data = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); + data.forEach(diagnostics => { + output.push('\n\n'); + output.push(`Remote: ${diagnostics.hostName}`); + output.push(this.formatMachineInfo(diagnostics.machineInfo)); + + if (diagnostics.processes) { + output.push(this.formatProcessList(info, diagnostics.processes)); + } + + if (diagnostics.workspaceMetadata) { + for (const folder of Object.keys(diagnostics.workspaceMetadata)) { + const metadata = diagnostics.workspaceMetadata[folder]; + + let countMessage = `${metadata.fileCount} files`; + if (metadata.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + + output.push(`Folder (${folder}): ${countMessage}`); + output.push(this.formatWorkspaceStats(metadata)); + } + } + }); + } catch (e) { + output.push('\n\n'); + output.push(`Fetching status information from remotes failed: ${e.message}`); + } + output.push(''); output.push(''); @@ -195,7 +265,7 @@ export class DiagnosticsService implements IDiagnosticsService { const workspaceStatPromises: Promise[] = []; info.windows.forEach(window => { - if (window.folderURIs.length === 0) { + if (window.folderURIs.length === 0 || !!window.remoteAuthority) { return; } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 2da8237266..8cbda0e31f 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -37,7 +37,7 @@ export interface ParsedArgs { logExtensionHostCommunication?: boolean; 'extensions-dir'?: string; 'builtin-extensions-dir'?: string; - extensionDevelopmentPath?: string; // either a local path or a URI + extensionDevelopmentPath?: string | string[]; // one or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI 'inspect-extensions'?: string; 'inspect-brk-extensions'?: string; @@ -124,7 +124,7 @@ export interface IEnvironmentService { disableExtensions: boolean | string[]; builtinExtensionsPath: string; extensionsPath: string; - extensionDevelopmentLocationURI?: URI; + extensionDevelopmentLocationURI?: URI | URI[]; extensionTestsLocationURI?: URI; debugExtensionHost: IExtensionHostDebugParams; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index ca3554de17..aeb54a484a 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -172,9 +172,16 @@ export class EnvironmentService implements IEnvironmentService { } @memoize - get extensionDevelopmentLocationURI(): URI | undefined { + get extensionDevelopmentLocationURI(): URI | URI[] | undefined { const s = this._args.extensionDevelopmentPath; - if (s) { + if (Array.isArray(s)) { + return s.map(p => { + if (/^[^:/?#]+?:\/\//.test(p)) { + return URI.parse(p); + } + return URI.file(path.normalize(p)); + }); + } else if (s) { if (/^[^:/?#]+?:\/\//.test(s)) { return URI.parse(s); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 219e52640a..bc3bb9d064 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -245,12 +245,6 @@ export interface IExtensionEnablementService { */ onEnablementChanged: Event; - /** - * Returns all disabled extension identifiers for current workspace - * Returns an empty array if none exist - */ - getDisabledExtensions(): Promise; - /** * Returns the enablement state for the given extension */ diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index c8b07e78d3..5d6eb8c52c 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -15,10 +15,11 @@ import { whenDeleted } from 'vs/base/node/pfs'; import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { BrowserWindow } from 'electron'; +import { BrowserWindow, ipcMain, Event as IpcEvent } from 'electron'; import { Event } from 'vs/base/common/event'; import { hasArgs } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; +import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -32,6 +33,7 @@ export interface IWindowInfo { pid: number; title: string; folderURIs: UriComponents[]; + remoteAuthority?: string; } export interface IMainProcessInfo { @@ -41,6 +43,11 @@ export interface IMainProcessInfo { windows: IWindowInfo[]; } +export interface IRemoteDiagnosticOptions { + includeProcesses?: boolean; + includeWorkspaceMetadata?: boolean; +} + function parseOpenUrl(args: ParsedArgs): URI[] { if (args['open-url'] && args._urls && args._urls.length > 0) { // --open-url must contain -- followed by the url(s) @@ -64,6 +71,7 @@ export interface ILaunchService { getMainProcessId(): Promise; getMainProcessInfo(): Promise; getLogsPath(): Promise; + getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise; } export class LaunchChannel implements IServerChannel { @@ -88,6 +96,9 @@ export class LaunchChannel implements IServerChannel { case 'get-logs-path': return this.service.getLogsPath(); + + case 'get-remote-diagnostics': + return this.service.getRemoteDiagnostics(arg); } throw new Error(`Call not found: ${command}`); @@ -115,6 +126,10 @@ export class LaunchChannelClient implements ILaunchService { getLogsPath(): Promise { return this.channel.call('get-logs-path', null); } + + getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise { + return this.channel.call('get-remote-diagnostics', options); + } } export class LaunchService implements ILaunchService { @@ -284,7 +299,45 @@ export class LaunchService implements ILaunchService { return Promise.resolve(this.environmentService.logsPath); } - private codeWindowToInfo(window: ICodeWindow): IWindowInfo { + getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise { + const windows = this.windowsMainService.getWindows(); + const promises: Promise[] = windows.map(window => { + return new Promise((resolve, reject) => { + if (window.remoteAuthority) { + const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`; + const args: IDiagnosticInfoOptions = { + includeProcesses: options.includeProcesses, + folders: options.includeWorkspaceMetadata ? this.getFolderURIs(window) : undefined + }; + + window.sendWhenReady('vscode:getDiagnosticInfo', { replyChannel, args }); + + ipcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => { + // No data is returned if getting the connection fails. + if (!data) { + resolve(); + } + + if (typeof (data) === 'string') { + reject(new Error(data)); + } + + resolve(data); + }); + + setTimeout(() => { + resolve(); + }, 5000); + } else { + resolve(); + } + }); + }); + + return Promise.all(promises).then(diagnostics => diagnostics.filter((x): x is IRemoteDiagnosticInfo => !!x)); + } + + private getFolderURIs(window: ICodeWindow): URI[] { const folderURIs: URI[] = []; if (window.openedFolderUri) { @@ -303,14 +356,20 @@ export class LaunchService implements ILaunchService { } } - return this.browserWindowToInfo(window.win, folderURIs); + return folderURIs; } - private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = []): IWindowInfo { + private codeWindowToInfo(window: ICodeWindow): IWindowInfo { + const folderURIs = this.getFolderURIs(window); + return this.browserWindowToInfo(window.win, folderURIs, window.remoteAuthority); + } + + private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = [], remoteAuthority?: string): IWindowInfo { return { pid: win.webContents.getOSProcessId(), title: win.getTitle(), - folderURIs + folderURIs, + remoteAuthority }; } } diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 7a85b6a2d6..5e08bbea91 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -63,7 +63,6 @@ export interface IRemoteExtensionHostStartParams { debugId?: string; break?: boolean; port?: number | null; - updatePort?: boolean; } interface IExtensionHostConnectionResult { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 140a325ed4..21c54fcb41 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -11,8 +11,6 @@ export interface ResolvedAuthority { readonly authority: string; readonly host: string; readonly port: number; - readonly debugListenPort?: number; - readonly debugConnectPort?: number; } export interface IRemoteAuthorityResolverService { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 1f0754bda9..45d6fd87a2 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -97,7 +97,7 @@ export interface IWindowsMainService { enterWorkspace(win: ICodeWindow, path: URI): Promise; closeWorkspace(win: ICodeWindow): void; open(openConfig: IOpenConfiguration): ICodeWindow[]; - openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void; + openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string | string[], openConfig: IOpenConfiguration): void; pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; pickFolderAndOpen(options: INativeOpenDialogOptions): Promise; pickFileAndOpen(options: INativeOpenDialogOptions): Promise; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f06455a725..312b905339 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -75,8 +75,6 @@ declare module 'vscode' { export class ResolvedAuthority { readonly host: string; readonly port: number; - debugListenPort?: number; - debugConnectPort?: number; constructor(host: string, port: number); } @@ -473,11 +471,6 @@ declare module 'vscode' { } export namespace workspace { - /** - * DEPRECATED - */ - export function registerSearchProvider(): Disposable; - /** * Register a search provider. * @@ -1017,9 +1010,7 @@ declare module 'vscode' { * Provide a list of ranges which allow new comment threads creation or null for a given document */ provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; - } - export interface EmptyCommentThreadFactory { /** * The method `createEmptyCommentThread` is called when users attempt to create new comment thread from the gutter or command palette. * Extensions still need to call `createCommentThread` inside this call when appropriate. @@ -1054,11 +1045,6 @@ declare module 'vscode' { */ commentingRangeProvider?: CommentingRangeProvider; - /** - * Optional new comment thread factory. - */ - emptyCommentThreadFactory?: EmptyCommentThreadFactory; - /** * Optional reaction provider */ diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 262c3c67b8..3a6de3bf26 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -357,6 +357,7 @@ export class MainThreadCommentController { return { owner: this._uniqueId, + label: this.label, threads: ret, commentingRanges: commentingRanges ? { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 3f91f28e3d..1b0aee3ab7 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -377,7 +377,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha command: data.m, // not-standard _id: data.x, - _pid: data.y }; } @@ -393,14 +392,14 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { suggestions: result.b.map(d => MainThreadLanguageFeatures._inflateSuggestDto(result.a, d)), incomplete: result.c, - dispose: () => this._proxy.$releaseCompletionItems(handle, result.x) + dispose: () => typeof result.x === 'number' && this._proxy.$releaseCompletionItems(handle, result.x) }; }); } }; if (supportsResolveDetails) { provider.resolveCompletionItem = (model, position, suggestion, token) => { - return this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion._id, suggestion._pid, token).then(result => { + return this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion._id, token).then(result => { if (!result) { return suggestion; } @@ -428,29 +427,36 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- links - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.LinkProviderRegistry.register(selector, { + $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void { + const provider: modes.LinkProvider = { provideLinks: (model, token) => { return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { - if (dto) { - dto.forEach(obj => { - MainThreadLanguageFeatures._reviveLinkDTO(obj); - this._heapService.trackObject(obj); - }); + if (!dto) { + return undefined; } - return dto; - }); - }, - resolveLink: (link, token) => { - return this._proxy.$resolveDocumentLink(handle, link, token).then(obj => { - if (obj) { - MainThreadLanguageFeatures._reviveLinkDTO(obj); - this._heapService.trackObject(obj); - } - return obj; + return { + links: dto.links.map(MainThreadLanguageFeatures._reviveLinkDTO), + dispose: () => { + if (typeof dto.id === 'number') { + this._proxy.$releaseDocumentLinks(handle, dto.id); + } + } + }; }); } - }); + }; + if (supportsResolve) { + provider.resolveLink = (link, token) => { + const dto: LinkDto = link; + if (!dto.cacheId) { + return link; + } + return this._proxy.$resolveDocumentLink(handle, dto.cacheId, token).then(obj => { + return obj && MainThreadLanguageFeatures._reviveLinkDTO(obj); + }); + }; + } + this._registrations[handle] = modes.LinkProviderRegistry.register(selector, provider); } // --- colors diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 8083a9d8a9..246856cba2 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -335,7 +335,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { } } - private async applyCodeActions(actionsToRun: ReadonlyArray) { + private async applyCodeActions(actionsToRun: readonly CodeAction[]) { for (const action of actionsToRun) { await applyCodeAction(action, this._bulkEditService, this._commandService); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 6a26d7be0f..01f1e498e3 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,9 +52,12 @@ import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; + appName: string; appRoot?: URI; + appLanguage: string; + appUriScheme: string; appSettingsHome?: URI; - extensionDevelopmentLocationURI?: URI; + extensionDevelopmentLocationURI?: URI | URI[]; extensionTestsLocationURI?: URI; globalStorageHome: URI; userHome: URI; @@ -71,6 +74,7 @@ export interface IWorkspaceData extends IStaticWorkspaceData { } export interface IInitData { + version: string; commit?: string; parentPid: number; environment: IEnvironment; @@ -318,8 +322,8 @@ export interface ISerializedDocumentFilter { } export interface ISerializedSignatureHelpProviderMetadata { - readonly triggerCharacters: ReadonlyArray; - readonly retriggerCharacters: ReadonlyArray; + readonly triggerCharacters: readonly string[]; + readonly retriggerCharacters: readonly string[]; } export interface MainThreadLanguageFeaturesShape extends IDisposable { @@ -343,7 +347,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportsResolveInitialValues: boolean): void; $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void; $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void; - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void; + $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void; $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; @@ -878,12 +882,11 @@ export interface SuggestDataDto { l/* additionalTextEdits */?: ISingleEditOperation[]; m/* command */?: modes.Command; // not-standard - x: number; - y: number; + x?: ChainedCacheId; } export interface SuggestResultDto { - x: number; + x?: number; a: IRange; b: SuggestDataDto[]; c?: boolean; @@ -961,7 +964,16 @@ export interface CodeActionDto { isPreferred?: boolean; } -export interface LinkDto extends ObjectIdentifier { +export type CacheId = number; +export type ChainedCacheId = [CacheId, CacheId]; + +export interface LinksListDto { + id?: CacheId; + links: LinkDto[]; +} + +export interface LinkDto { + cacheId?: ChainedCacheId; range: IRange; url?: string | UriComponents; } @@ -1007,11 +1019,12 @@ export interface ExtHostLanguageFeaturesShape { $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: number, pid: number, token: CancellationToken): Promise; + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveDocumentLink(handle: number, link: LinkDto, token: CancellationToken): Promise; + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; + $releaseDocumentLinks(handle: number, id: number): void; $provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index 04ccf2cdeb..9bd51b0ad2 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -25,7 +25,6 @@ import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/we import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { extHostNamedCustomer } from '../common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadWebviews) @@ -61,7 +60,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews @ITelemetryService private readonly _telemetryService: ITelemetryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -136,7 +134,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews // 4) continue to forward messages to the webview const webview = this._instantiationService.createInstance( WebviewElement, - this._layoutService.getContainer(Parts.EDITOR_PART), { extension: { location: URI.revive(extensionLocation), diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 9afb52eb93..22f47c6ae1 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -9,7 +9,6 @@ import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import * as path from 'vs/base/common/path'; -import * as platform from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; @@ -17,8 +16,6 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; import { ExtHostContext, IInitData, IMainContext, MainContext, MainThreadKeytarShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/node/extHostClipboard'; @@ -27,6 +24,8 @@ import { ExtHostComments } from 'vs/workbench/api/node/extHostComments'; import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration'; // {{SQL CARBON EDIT}} - Remove ExtHostDebugService // import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; +// {{SQL CARBON EDIT}} - Import product +import product from 'vs/platform/product/node/product'; import { ExtHostDecorations } from 'vs/workbench/api/node/extHostDecorations'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs'; @@ -68,6 +67,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { originalFSPath } from 'vs/base/common/resources'; import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { values } from 'vs/base/common/collections'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -134,7 +134,7 @@ export function createApiFactory( } // Check that no named customers are missing - const expected: ProxyIdentifier[] = Object.keys(ExtHostContext).map((key) => (ExtHostContext)[key]); + const expected: ProxyIdentifier[] = values(ExtHostContext); rpcProtocol.assertRegistered(expected); // Other instances @@ -234,10 +234,10 @@ export function createApiFactory( const env: typeof vscode.env = Object.freeze({ get machineId() { return initData.telemetryInfo.machineId; }, get sessionId() { return initData.telemetryInfo.sessionId; }, - get language() { return platform.language!; }, - get appName() { return product.nameLong; }, + get language() { return initData.environment.appLanguage; }, + get appName() { return initData.environment.appName; }, get appRoot() { return initData.environment.appRoot!.fsPath; }, - get uriScheme() { return product.urlProtocol; }, + get uriScheme() { return initData.environment.appUriScheme; }, get logLevel() { checkProposedApiEnabled(extension); return typeConverters.LogLevel.to(extHostLogService.getLevel()); @@ -628,10 +628,6 @@ export function createApiFactory( registerFileSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.FileSearchProvider) => { return extHostSearch.registerFileSearchProvider(scheme, provider); }), - registerSearchProvider: proposedApiFunction(extension, () => { - // Temp for live share in Insiders - return { dispose: () => { } }; - }), registerTextSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.TextSearchProvider) => { return extHostSearch.registerTextSearchProvider(scheme, provider); }), diff --git a/src/vs/workbench/api/node/extHostComments.ts b/src/vs/workbench/api/node/extHostComments.ts index 9ffa42fbeb..d710cc1b2b 100644 --- a/src/vs/workbench/api/node/extHostComments.ts +++ b/src/vs/workbench/api/node/extHostComments.ts @@ -149,12 +149,25 @@ export class ExtHostComments implements ExtHostCommentsShape { $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise { const commentController = this._commentControllers.get(commentControllerHandle); - if (!commentController || !commentController.emptyCommentThreadFactory) { + if (!commentController) { + return Promise.resolve(); + } + + if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread)) { return Promise.resolve(); } const document = this._documents.getDocument(URI.revive(uriComponents)); - return asPromise(() => commentController.emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range))).then(() => Promise.resolve()); + return asPromise(() => { + // TODO, remove this once GH PR stable deprecates `emptyCommentThreadFactory`. + if ((commentController as any).emptyCommentThreadFactory) { + return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); + } + + if (commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) { + return commentController.commentingRangeProvider.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); + } + }).then(() => Promise.resolve()); } registerWorkspaceCommentProvider( @@ -558,8 +571,6 @@ class ExtHostCommentController implements vscode.CommentController { private _threads: Map = new Map(); commentingRangeProvider?: vscode.CommentingRangeProvider; - emptyCommentThreadFactory?: vscode.EmptyCommentThreadFactory; - private _commentReactionProvider?: vscode.CommentReactionProvider; diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index a65e2d2652..85ddfef924 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -707,8 +707,6 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { authority: remoteAuthority, host: result.host, port: result.port, - debugListenPort: result.debugListenPort, - debugConnectPort: result.debugConnectPort, }; } diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index b227d757b9..13da1959e5 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, MainThreadWebviewsShape, CodeInsetDto, SuggestDataDto } from '../common/extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, MainThreadWebviewsShape, CodeInsetDto, SuggestDataDto, LinksListDto, ChainedCacheId } from '../common/extHost.protocol'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; @@ -623,8 +623,7 @@ class SuggestAdapter { private _commands: CommandsConverter; private _provider: vscode.CompletionItemProvider; - private _cache = new Map(); - private _idPool = 0; + private _cache = new Cache(); constructor(documents: ExtHostDocuments, commands: CommandsConverter, provider: vscode.CompletionItemProvider) { this._documents = documents; @@ -639,33 +638,32 @@ class SuggestAdapter { return asPromise(() => this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context))).then(value => { - const _id = this._idPool++; + if (!value) { + // undefined and null are valid results + return undefined; + } + + let list = Array.isArray(value) ? new CompletionList(value) : value; + let pid: number | undefined; + + // keep result for providers that support resolving + if (SuggestAdapter.supportsResolving(this._provider)) { + pid = this._cache.add(list.items); + } // the default text edit range const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)) .with({ end: pos }); const result: SuggestResultDto = { - x: _id, + x: pid, b: [], a: typeConvert.Range.from(wordRangeBeforePos), + c: list.isIncomplete || undefined }; - let list: CompletionList; - if (!value) { - // undefined and null are valid results - return undefined; - - } else if (Array.isArray(value)) { - list = new CompletionList(value); - - } else { - list = value; - result.c = list.isIncomplete; - } - for (let i = 0; i < list.items.length; i++) { - const suggestion = this._convertCompletionItem2(list.items[i], pos, i, _id); + const suggestion = this._convertCompletionItem(list.items[i], pos, pid && [pid, i] || undefined); // check for bad completion item // for the converter did warn if (suggestion) { @@ -673,21 +671,17 @@ class SuggestAdapter { } } - if (SuggestAdapter.supportsResolving(this._provider)) { - this._cache.set(_id, list.items); - } - return result; }); } - resolveCompletionItem(_resource: URI, position: IPosition, id: number, pid: number, token: CancellationToken): Promise { + resolveCompletionItem(_resource: URI, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveCompletionItem !== 'function') { return Promise.resolve(undefined); } - const item = this._cache.has(pid) ? this._cache.get(pid)![id] : undefined; + const item = this._cache.get(...id); if (!item) { return Promise.resolve(undefined); } @@ -699,7 +693,7 @@ class SuggestAdapter { } const pos = typeConvert.Position.to(position); - return this._convertCompletionItem2(resolvedItem, pos, id, pid); + return this._convertCompletionItem(resolvedItem, pos, id); }); } @@ -707,7 +701,7 @@ class SuggestAdapter { this._cache.delete(id); } - private _convertCompletionItem2(item: vscode.CompletionItem, position: vscode.Position, id: number, pid: number): SuggestDataDto | undefined { + private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId | undefined): SuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { console.warn('INVALID text edit -> must have at least a label'); return undefined; @@ -716,7 +710,6 @@ class SuggestAdapter { const result: SuggestDataDto = { // x: id, - y: pid, // a: item.label, b: typeConvert.CompletionItemKind.from(item.kind), @@ -800,49 +793,78 @@ class SignatureHelpAdapter { } } +class Cache { + + private _data = new Map(); + private _idPool = 1; + + add(item: T[]): number { + const id = this._idPool++; + this._data.set(id, item); + return id; + } + + get(pid: number, id: number): T | undefined { + return this._data.has(pid) ? this._data.get(pid)![id] : undefined; + } + + delete(id: number) { + this._data.delete(id); + } +} + class LinkProviderAdapter { + private _cache = new Cache(); + constructor( private readonly _documents: ExtHostDocuments, - private readonly _heapService: ExtHostHeapService, private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { + provideLinks(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { - if (!Array.isArray(links)) { + if (!Array.isArray(links) || links.length === 0) { + // bad result return undefined; } - const result: LinkDto[] = []; - for (const link of links) { - const data = typeConvert.DocumentLink.from(link); - const id = this._heapService.keep(link); - result.push(ObjectIdentifier.mixin(data, id)); + + if (typeof this._provider.resolveDocumentLink !== 'function') { + // no resolve -> no caching + return { links: links.map(typeConvert.DocumentLink.from) }; + + } else { + // cache links for future resolving + const pid = this._cache.add(links); + const result: LinksListDto = { links: [], id: pid }; + for (let i = 0; i < links.length; i++) { + const dto: LinkDto = typeConvert.DocumentLink.from(links[i]); + dto.cacheId = [pid, i]; + result.links.push(dto); + } + return result; } - return result; }); } - resolveLink(link: LinkDto, token: CancellationToken): Promise { + resolveLink(id: ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveDocumentLink !== 'function') { return Promise.resolve(undefined); } - - const id = ObjectIdentifier.of(link); - const item = this._heapService.get(id); + const item = this._cache.get(...id); if (!item) { return Promise.resolve(undefined); } - return asPromise(() => this._provider.resolveDocumentLink!(item, token)).then(value => { - if (value) { - return typeConvert.DocumentLink.from(value); - } - return undefined; + return value && typeConvert.DocumentLink.from(value) || undefined; }); } + + releaseLinks(id: number): any { + this._cache.delete(id); + } } class ColorProviderAdapter { @@ -1373,8 +1395,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context, token), undefined); } - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: number, pid: number, token: CancellationToken): Promise { - return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(URI.revive(resource), position, id, pid, token), undefined); + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { + return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(URI.revive(resource), position, id, token), undefined); } $releaseCompletionItems(handle: number, id: number): void { @@ -1400,17 +1422,21 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- links registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { - const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, this._heapService, provider), extension); - this._proxy.$registerDocumentLinkProvider(handle, this._transformDocumentSelector(selector)); + const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, provider), extension); + this._proxy.$registerDocumentLinkProvider(handle, this._transformDocumentSelector(selector), typeof provider.resolveDocumentLink === 'function'); return this._createDisposable(handle); } - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(URI.revive(resource), token), undefined); } - $resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Promise { - return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(link, token), undefined); + $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise { + return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(id, token), undefined); + } + + $releaseDocumentLinks(handle: number, id: number): void { + this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.releaseLinks(id), undefined); } registerColorProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 68df6f1151..07667e0d6e 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -432,8 +432,6 @@ export class Selection extends Range { export class ResolvedAuthority { readonly host: string; readonly port: number; - debugListenPort?: number; - debugConnectPort?: number; constructor(host: string, port: number) { if (typeof host !== 'string' || host.length === 0) { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 2893ef03bc..c562637f54 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { toResource, IEditorInput } from 'vs/workbench/common/editor'; +import { toResource, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -332,7 +332,7 @@ class ResourceLabelWidget extends IconLabel { setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ - resource: withNullAsUndefined(toResource(editor, { supportSideBySide: true })), + resource: withNullAsUndefined(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })), name: withNullAsUndefined(editor.getName()), description: withNullAsUndefined(editor.getDescription()) }, options); diff --git a/src/vs/workbench/browser/nodeless.main.ts b/src/vs/workbench/browser/nodeless.main.ts index af6086861e..cdfe9e5423 100644 --- a/src/vs/workbench/browser/nodeless.main.ts +++ b/src/vs/workbench/browser/nodeless.main.ts @@ -45,6 +45,11 @@ class CodeRendererMain extends Disposable { const logService = new SimpleLogService(); serviceCollection.set(ILogService, logService); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. + // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + return { serviceCollection, logService }; } } diff --git a/src/vs/workbench/browser/nodeless.simpleservices.ts b/src/vs/workbench/browser/nodeless.simpleservices.ts index 3c87c60a4f..982cf20263 100644 --- a/src/vs/workbench/browser/nodeless.simpleservices.ts +++ b/src/vs/workbench/browser/nodeless.simpleservices.ts @@ -18,9 +18,9 @@ import { SimpleConfigurationService as StandaloneEditorConfigurationService, Sta import { IDownloadService } from 'vs/platform/download/common/download'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEnvironmentService, IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment/common/environment'; -import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOperation, StatisticType, ITranslation, IGalleryExtensionVersion, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata, IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOperation, StatisticType, ITranslation, IGalleryExtensionVersion, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata, IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation, IExtensionEnablementService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IPager } from 'vs/base/common/paging'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions'; import { NullExtensionService, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -251,7 +251,7 @@ export class SimpleEnvironmentService implements IEnvironmentService { disableExtensions: boolean | string[]; builtinExtensionsPath: string; extensionsPath: string; - extensionDevelopmentLocationURI?: URI; + extensionDevelopmentLocationURI?: URI | URI[]; extensionTestsPath?: string; debugExtensionHost: IExtensionHostDebugParams; debugSearch: IDebugParams; @@ -354,6 +354,38 @@ registerSingleton(IExtensionGalleryService, SimpleExtensionGalleryService, true) //#region Extension Management +//#region Extension Enablement + +export class SimpleExtensionEnablementService implements IExtensionEnablementService { + + _serviceBrand: any; + + readonly onEnablementChanged = Event.None; + + readonly allUserExtensionsDisabled = true; + + getEnablementState(extension: IExtension): EnablementState { + return EnablementState.Disabled; + } + + canChangeEnablement(extension: IExtension): boolean { + return false; + } + + setEnablement(extensions: IExtension[], newState: EnablementState): Promise { + throw new Error('not implemented'); + } + + isEnabled(extension: IExtension): boolean { + return false; + } + +} + +registerSingleton(IExtensionEnablementService, SimpleExtensionEnablementService, true); + +//#endregion + //#region Extension Tips export class SimpleExtensionTipsService implements IExtensionTipsService { diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index fb24f033bf..81131222fd 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -73,6 +73,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this.visibleComposites = []; this.compositeSizeInBar = new Map(); this.compositeTransfer = LocalSelectionTransfer.getInstance(); + this.computeSizes(this.model.visibleItems); } getCompositeBarItems(): ICompositeBarItem[] { diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 850d7bb2cb..a718c15fcd 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -15,7 +15,7 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, toResource } from 'vs/workbench/common/editor'; +import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -50,7 +50,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { } getResource() { - return toResource(this.editor, { supportSideBySide: true }); + return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); } getAriaLabel(): string { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 2a402d0d85..e81b5bd56e 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -14,7 +14,7 @@ import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; @@ -772,7 +772,7 @@ export class EditorStatus implements IStatusbarItem { private onResourceEncodingChange(resource: URI): void { const activeControl = this.editorService.activeControl; if (activeControl) { - const activeResource = toResource(activeControl.input, { supportSideBySide: true }); + const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); if (activeResource && activeResource.toString() === resource.toString()) { return this.onEncodingChange(activeControl); // only update if the encoding changed for the active resource } @@ -850,7 +850,7 @@ export class ChangeModeAction extends Action { } const textModel = activeTextEditorWidget.getModel(); - const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: true }) : null; + const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; if (resource && resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { @@ -976,7 +976,7 @@ export class ChangeModeAction extends Action { if (pick === autoDetectMode) { if (textModel) { // {{SQL CARBON EDIT}} - use activeEditor.input instead of activeEditor - const resource = toResource(activeEditor.input, { supportSideBySide: true }); + const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); } @@ -1195,7 +1195,7 @@ export class ChangeEncodingAction extends Action { return undefined; } - const resource = toResource(activeControl!.input, { supportSideBySide: true }); + const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); return timeout(50 /* quick open is sensitive to being opened so soon after another */) .then(() => { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 70eaf650d3..b6aa4f9ba9 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notabstitlecontrol'; -import { toResource, Verbosity, IEditorInput, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { toResource, Verbosity, IEditorInput, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; @@ -237,7 +237,7 @@ export class NoTabsTitleControl extends TitleControl { this.updateEditorDirty(editor); // Editor Label - const resource = toResource(editor, { supportSideBySide: true }); + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); const name = editor.getName() || ''; const { labelFormat } = this.accessor.partOptions; diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 0e18690a19..e85056a8d8 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -21,6 +21,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; export class SideBySideEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.sidebysideEditor'; + static MASTER: SideBySideEditor | undefined; get minimumMasterWidth() { return this.masterEditor ? this.masterEditor.minimumWidth : 0; } get maximumMasterWidth() { return this.masterEditor ? this.masterEditor.maximumWidth : Number.POSITIVE_INFINITY; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 112629d3ca..e87f4fdee9 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/tabstitlecontrol'; import { isMacintosh } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; -import { toResource, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { toResource, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -608,7 +608,7 @@ export class TabsTitleControl extends TitleControl { e.dataTransfer!.effectAllowed = 'copyMove'; // Apply some datatransfer types to allow for dragging the element outside of the application - const resource = toResource(editor, { supportSideBySide: true }); + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e); } @@ -899,7 +899,7 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: true }) || undefined }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }) || undefined }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); // {{SQL CARBON EDIT}} -- Display the editor's tab color const isTabActive = this.group.isActive(editor); diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index e57872bfb5..d08aae2684 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -33,7 +33,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -219,7 +219,7 @@ export abstract class TitleControl extends Themable { this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); // Update the resource context - this.resourceContext.set(this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: true }) : null); + this.resourceContext.set(this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null); // Editor actions require the editor control to be there, so we retrieve it via service const activeControl = this.group.activeControl; @@ -259,7 +259,7 @@ export abstract class TitleControl extends Themable { // If tabs are disabled, treat dragging as if an editor tab was dragged if (!this.accessor.partOptions.showTabs) { - const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: true }) : null; + const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; if (resource) { this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e); } @@ -286,7 +286,7 @@ export abstract class TitleControl extends Themable { // Update the resource context const currentContext = this.resourceContext.get(); - this.resourceContext.set(toResource(editor, { supportSideBySide: true })); + this.resourceContext.set(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })); // Find target anchor let anchor: HTMLElement | { x: number, y: number } = node; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 8344199e67..41745f6c05 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -191,6 +191,10 @@ export class NotificationsToasts extends Themable { // Update when item height potentially changes due to label changes itemDisposeables.push(item.onDidLabelChange(e => { + if (!item.expanded) { + return; // dynamic height only applies to expanded notifications + } + if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) { notificationList.updateNotificationsList(0, 1, [item]); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 6f6fafbf74..b1fe710959 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -350,6 +350,9 @@ export class NotificationTemplateRenderer { case NotificationViewItemLabelKind.PROGRESS: this.renderProgress(notification); break; + case NotificationViewItemLabelKind.MESSAGE: + this.renderMessage(notification); + break; } })); } diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index c82a29fcb0..4af7b828af 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -45,10 +45,12 @@ } /* adding padding to the most left status bar item */ -.monaco-workbench .part.statusbar > .statusbar-item.left:first-child, .monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.left { +.monaco-workbench .part.statusbar > .statusbar-item.left:first-child, +.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.left { padding-left: 7px; } -.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left:first-child, .monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.has-background-color.left { +.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left:first-child, +.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.has-background-color.left { padding-right: 7px; /* expand padding if background color is configured for the status bar entry to make it look centered properly */ } /* adding padding to the most right status bar item */ diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 95ab02b3be..9331b78df7 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -17,7 +17,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; -import { EditorInput, toResource, Verbosity } from 'vs/workbench/common/editor'; +import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -179,7 +179,7 @@ export class TitlebarPart extends Part implements ITitleService { } private updateRepresentedFilename(): void { - const file = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: 'file' }); + const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: 'file' }); const path = file ? file.fsPath : ''; // Apply to window @@ -282,7 +282,7 @@ export class TitlebarPart extends Part implements ITitleService { // Compute folder resource // Single Root Workspace: always the root single workspace in this case // Otherwise: root folder of the currently active file if any - const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: true })!); + const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })!); // Variables const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : ''; diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index b745e76e57..f6b1d615f3 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -205,8 +205,7 @@ export class Workbench extends Layout { // TODO@Sandeep debt around cyclic dependencies const configurationService = accessor.get(IConfigurationService) as any; - if (typeof configurationService.acquireFileService === 'function') { - configurationService.acquireFileService(fileService); + if (typeof configurationService.acquireInstantiationService === 'function') { configurationService.acquireInstantiationService(instantiationService); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 3a04999fa8..419c4fa1ba 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import * as objects from 'vs/base/common/objects'; -import * as types from 'vs/base/common/types'; +import { assign } from 'vs/base/common/objects'; +import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -14,7 +14,6 @@ import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; -import { Schemas } from 'vs/base/common/network'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; @@ -582,7 +581,7 @@ export class SideBySideEditorInput extends EditorInput { getTelemetryDescriptor(): object { const descriptor = this.master.getTelemetryDescriptor(); - return objects.assign(descriptor, super.getTelemetryDescriptor()); + return assign(descriptor, super.getTelemetryDescriptor()); } private registerListeners(): void { @@ -838,7 +837,7 @@ export class TextEditorOptions extends EditorOptions { * Returns if this options object has objects defined for the editor. */ hasOptionsDefined(): boolean { - return !!this.editorViewState || (!types.isUndefinedOrNull(this.startLineNumber) && !types.isUndefinedOrNull(this.startColumn)); + return !!this.editorViewState || (!isUndefinedOrNull(this.startLineNumber) && !isUndefinedOrNull(this.startColumn)); } /** @@ -886,10 +885,10 @@ export class TextEditorOptions extends EditorOptions { } // Otherwise check for selection - else if (!types.isUndefinedOrNull(this.startLineNumber) && !types.isUndefinedOrNull(this.startColumn)) { + else if (!isUndefinedOrNull(this.startLineNumber) && !isUndefinedOrNull(this.startColumn)) { // Select - if (!types.isUndefinedOrNull(this.endLineNumber) && !types.isUndefinedOrNull(this.endColumn)) { + if (!isUndefinedOrNull(this.endLineNumber) && !isUndefinedOrNull(this.endColumn)) { const range = { startLineNumber: this.startLineNumber, startColumn: this.startColumn, @@ -990,9 +989,14 @@ export interface IEditorPartOptions extends IEditorPartConfiguration { iconTheme?: string; } +export enum SideBySideEditor { + MASTER = 1, + DETAILS = 2 +} + export interface IResourceOptions { - supportSideBySide?: boolean; - filter?: string | string[]; + supportSideBySide?: SideBySideEditor; + filterByScheme?: string | string[]; } export function toResource(editor: IEditorInput | null | undefined, options?: IResourceOptions): URI | null { @@ -1000,35 +1004,20 @@ export function toResource(editor: IEditorInput | null | undefined, options?: IR return null; } - // Check for side by side if we are asked to if (options && options.supportSideBySide && editor instanceof SideBySideEditorInput) { - editor = editor.master; + editor = options.supportSideBySide === SideBySideEditor.MASTER ? editor.master : editor.details; } const resource = editor.getResource(); - if (!options || !options.filter) { - return types.withUndefinedAsNull(resource); // return early if no filter is specified + if (!resource || !options || !options.filterByScheme) { + return withUndefinedAsNull(resource); } - if (!resource) { - return null; - } - - let includeFiles: boolean; - let includeUntitled: boolean; - if (Array.isArray(options.filter)) { - includeFiles = (options.filter.indexOf(Schemas.file) >= 0); - includeUntitled = (options.filter.indexOf(Schemas.untitled) >= 0); - } else { - includeFiles = (options.filter === Schemas.file); - includeUntitled = (options.filter === Schemas.untitled); - } - - if (includeFiles && resource.scheme === Schemas.file) { + if (Array.isArray(options.filterByScheme) && options.filterByScheme.some(scheme => resource.scheme === scheme)) { return resource; } - if (includeUntitled && resource.scheme === Schemas.untitled) { + if (options.filterByScheme === resource.scheme) { return resource; } diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 297a9a3253..c5958ff2bb 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorInput } from 'vs/workbench/common/editor'; +import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -157,7 +157,7 @@ export class EditorGroup extends Disposable { } for (const editor of this.editors) { - const editorResource = toResource(editor, { supportSideBySide: true }); + const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (editorResource && editorResource.toString() === resource.toString()) { return editor; } @@ -541,7 +541,7 @@ export class EditorGroup extends Disposable { } private updateResourceMap(editor: EditorInput, remove: boolean): void { - const resource = toResource(editor, { supportSideBySide: true }); + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { // It is possible to have the same resource opened twice (once as normal input and once as diff input) diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index bf78c1b654..8aa73ed758 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -24,6 +24,7 @@ export interface IResourceCommentThreadEvent { export interface ICommentInfo extends CommentInfo { owner: string; + label?: string; } export interface IWorkspaceCommentThreadsEvent { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index ad0f96ad6b..c0c3cb2ac4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -34,6 +34,9 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { IAction, Action } from 'vs/base/common/actions'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; +import { IQuickInputService, QuickPickInput, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; export const ID = 'editor.contrib.review'; @@ -61,7 +64,7 @@ class CommentingRangeDecoration { return this._decorationId; } - constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) { + constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) { const startLineNumber = _range.startLineNumber; const endLineNumber = _range.endLineNumber; let commentingRangeDecorations = [{ @@ -78,9 +81,10 @@ class CommentingRangeDecoration { } } - public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } { + public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } { return { extensionId: this._extensionId, + label: this._label, replyCommand: this._reply, ownerId: this._ownerId, commentingRangesInfo: this.commentingRangesInfo @@ -120,11 +124,11 @@ class CommentingRangeDecorator { for (const info of commentInfos) { if (Array.isArray(info.commentingRanges)) { info.commentingRanges.forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, range, info.reply, this.decorationOptions)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions)); }); } else { (info.commentingRanges ? info.commentingRanges.ranges : []).forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, range, (info.commentingRanges as modes.CommentingRanges).newCommentThreadCommand, this.decorationOptions, info.commentingRanges as modes.CommentingRanges)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, (info.commentingRanges as modes.CommentingRanges).newCommentThreadCommand, this.decorationOptions, info.commentingRanges as modes.CommentingRanges)); }); } } @@ -136,14 +140,15 @@ class CommentingRangeDecorator { } public getMatchedCommentAction(line: number) { + let result = []; for (const decoration of this.commentingRangeDecorations) { const range = decoration.getActiveRange(); if (range && range.startLineNumber <= line && line <= range.endLineNumber) { - return decoration.getCommentAction(); + result.push(decoration.getCommentAction()); } } - return null; + return result; } public dispose(): void { @@ -164,7 +169,7 @@ export class ReviewController implements IEditorContribution { private _commentingRangeSpaceReserved = false; private _computePromise: CancelablePromise> | null; private _addInProgress: boolean; - private _emptyThreadsToAddQueue: number[] = []; + private _emptyThreadsToAddQueue: [number, IEditorMouseEvent | undefined][] = []; private _computeCommentingRangePromise: CancelablePromise | null; private _computeCommentingRangeScheduler: Delayer> | null; private _pendingCommentCache: { [key: number]: { [key: string]: string } }; @@ -178,6 +183,7 @@ export class ReviewController implements IEditorContribution { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IContextMenuService readonly contextMenuService: IContextMenuService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { this.editor = editor; this.globalToDispose = []; @@ -547,11 +553,11 @@ export class ReviewController implements IEditorContribution { if (e.target.element.className.indexOf('comment-diff-added') >= 0) { const lineNumber = e.target.position!.lineNumber; - this.addOrToggleCommentAtLine(lineNumber); + this.addOrToggleCommentAtLine(lineNumber, e); } } - public async addOrToggleCommentAtLine(lineNumber: number): Promise { + public async addOrToggleCommentAtLine(lineNumber: number, e: IEditorMouseEvent | undefined): Promise { // If an add is already in progress, queue the next add and process it after the current one finishes to // prevent empty comment threads from being added to the same line. if (!this._addInProgress) { @@ -563,29 +569,98 @@ export class ReviewController implements IEditorContribution { this.processNextThreadToAdd(); return; } else { - this.addCommentAtLine(lineNumber); + this.addCommentAtLine(lineNumber, e); } } else { - this._emptyThreadsToAddQueue.push(lineNumber); + this._emptyThreadsToAddQueue.push([lineNumber, e]); } } private processNextThreadToAdd(): void { this._addInProgress = false; - const lineNumber = this._emptyThreadsToAddQueue.shift(); - if (lineNumber) { - this.addOrToggleCommentAtLine(lineNumber); + const info = this._emptyThreadsToAddQueue.shift(); + if (info) { + this.addOrToggleCommentAtLine(info[0], info[1]); } } - public addCommentAtLine(lineNumber: number): Promise { - const newCommentInfo = this._commentingRangeDecorator.getMatchedCommentAction(lineNumber); - if (!newCommentInfo || !this.editor.hasModel()) { + public addCommentAtLine(lineNumber: number, e: IEditorMouseEvent | undefined): Promise { + const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(lineNumber); + if (!newCommentInfos.length || !this.editor.hasModel()) { return Promise.resolve(); } - const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfo; + if (newCommentInfos.length > 1) { + if (e) { + const anchor = { x: e.event.posx, y: e.event.posy }; + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => this.getContextMenuActions(newCommentInfos, lineNumber), + getActionsContext: () => newCommentInfos.length ? newCommentInfos[0] : undefined, + onHide: () => { this._addInProgress = false; } + }); + + return Promise.resolve(); + } else { + const picks = this.getCommentProvidersQuickPicks(newCommentInfos); + return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickCommentService', "Select Comment Provider"), matchOnDescription: true }).then(pick => { + if (!pick) { + return; + } + + const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id); + + if (commentInfos.length) { + const { replyCommand, ownerId, extensionId, commentingRangesInfo } = commentInfos[0]; + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + } + }).then(() => { + this._addInProgress = false; + }); + } + } else { + const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfos[0]!; + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + } + + return Promise.resolve(); + } + + private getCommentProvidersQuickPicks(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[]) { + const picks: QuickPickInput[] = commentInfos.map((commentInfo) => { + const { ownerId, extensionId, label } = commentInfo; + + return { + label: label || extensionId, + id: ownerId + }; + }); + + return picks; + } + + private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] { + const actions: (IAction | ContextSubMenu)[] = []; + + commentInfos.forEach(commentInfo => { + const { replyCommand, ownerId, extensionId, label, commentingRangesInfo } = commentInfo; + + actions.push(new Action( + 'addCommentThread', + `${label || extensionId}`, + undefined, + true, + () => { + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + return Promise.resolve(); + } + )); + }); + return actions; + } + + public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined) { if (commentingRangesInfo) { let range = new Range(lineNumber, 1, lineNumber, 1); if (commentingRangesInfo.newCommentThreadCommand) { @@ -597,7 +672,7 @@ export class ReviewController implements IEditorContribution { this._addInProgress = false; } } else if (commentingRangesInfo.newCommentThreadCallback) { - return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel().uri, range) + return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range) .then(_ => { this.processNextThreadToAdd(); }) @@ -621,7 +696,6 @@ export class ReviewController implements IEditorContribution { return Promise.resolve(); } - private setComments(commentInfos: ICommentInfo[]): void { if (!this.editor) { return; @@ -762,7 +836,7 @@ CommandsRegistry.registerCommand({ } const position = activeEditor.getPosition(); - return controller.addOrToggleCommentAtLine(position.lineNumber); + return controller.addOrToggleCommentAtLine(position.lineNumber, undefined); } }); diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 96bf6ee8c8..bd9997c144 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -797,7 +797,7 @@ export class DebugModel implements IDebugModel { // Make sure to de-dupe if a session is re-intialized. In case of EH debugging we are adding a session again after an attach. return false; } - if (s.state === State.Inactive && s.getLabel() === session.getLabel()) { + if (s.state === State.Inactive && s.configuration.name === session.configuration.name) { // Make sure to remove all inactive sessions that are using the same configuration as the new session return false; } diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts index f5318e3438..ea0e3666c6 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts @@ -783,6 +783,7 @@ export class DebugSession implements IDebugSession { })); this.rawListeners.push(this.raw.onDidExitAdapter(event => { + this.initialized = true; this._onDidEndAdapter.fire(event); })); } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 5abcb41837..4e3a6e3f18 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -14,7 +14,7 @@ import { } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { Emitter } from 'vs/base/common/event'; -import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; +import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { URLService } from 'vs/platform/url/common/urlService'; import { IURLService } from 'vs/platform/url/common/url'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 8c7d801dfc..f087e22e67 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -83,30 +83,30 @@ suite('Experimental Prompts', () => { }); - test('Show experimental prompt if experiment should be run. Choosing option with link should mark experiment as complete', () => { + // test('Show experimental prompt if experiment should be run. Choosing option with link should mark experiment as complete', () => { - storageData = { - enabled: true, - state: ExperimentState.Run - }; + // storageData = { + // enabled: true, + // state: ExperimentState.Run + // }; - instantiationService.stub(INotificationService, { - prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { - assert.equal(b, promptText); - assert.equal(c.length, 2); - c[0].run(); - return undefined!; - } - }); + // instantiationService.stub(INotificationService, { + // prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { + // assert.equal(b, promptText); + // assert.equal(c.length, 2); + // c[0].run(); + // return undefined!; + // } + // }); - experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); - onExperimentEnabledEvent.fire(experiment); + // experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); + // onExperimentEnabledEvent.fire(experiment); - return Promise.resolve(null).then(result => { - assert.equal(storageData['state'], ExperimentState.Complete); - }); + // return Promise.resolve(null).then(result => { + // assert.equal(storageData['state'], ExperimentState.Complete); + // }); - }); + // }); test('Show experimental prompt if experiment should be run. Choosing negative option should mark experiment as complete', () => { diff --git a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts index d3b08e20ce..d33787e72c 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts @@ -115,20 +115,16 @@ export function onExtensionChanged(accessor: ServicesAccessor): Event { +export async function getInstalledExtensions(accessor: ServicesAccessor): Promise { const extensionService = accessor.get(IExtensionManagementService); const extensionEnablementService = accessor.get(IExtensionEnablementService); - return extensionService.getInstalled().then(extensions => { - return extensionEnablementService.getDisabledExtensions() - .then(disabledExtensions => { - return extensions.map(extension => { - return { - identifier: extension.identifier, - local: extension, - globallyEnabled: disabledExtensions.every(disabled => !areSameExtensions(disabled, extension.identifier)) - }; - }); - }); + const extensions = await extensionService.getInstalled(); + return extensions.map(extension => { + return { + identifier: extension.identifier, + local: extension, + globallyEnabled: extensionEnablementService.isEnabled(extension) + }; }); } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index 96190e0e15..a6e9aa1a4f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -33,7 +33,6 @@ import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/we import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -200,7 +199,6 @@ export class ExtensionEditor extends BaseEditor { @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @IOpenerService private readonly openerService: IOpenerService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, @IStorageService storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @@ -546,7 +544,6 @@ export class ExtensionEditor extends BaseEditor { .then(removeEmbeddedSVGs) .then(body => { const wbeviewElement = this.instantiationService.createInstance(WebviewElement, - this.layoutService.getContainer(Parts.EDITOR_PART), { enableFindWidget: true, }, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index e5f3d81fdd..b8d769f00f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/extensionActions'; import { localize } from 'vs/nls'; import { IAction, Action } from 'vs/base/common/actions'; -import { Throttler, Delayer } from 'vs/base/common/async'; +import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; @@ -1044,9 +1044,10 @@ export class ReloadAction extends ExtensionAction { private static readonly EnabledClass = 'extension-action reload'; private static readonly DisabledClass = `${ReloadAction.EnabledClass} disabled`; - // Use delayer to wait for more updates - private throttler: Throttler; private disposables: IDisposable[] = []; + private _runningExtensions: IExtensionDescription[] = []; + private get runningExtensions(): IExtensionDescription[] { return this._runningExtensions; } + private set runningExtensions(runningExtensions: IExtensionDescription[]) { this._runningExtensions = runningExtensions; this.update(); } constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -1055,39 +1056,38 @@ export class ReloadAction extends ExtensionAction { @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); - this.throttler = new Throttler(); - this.extensionService.onDidChangeExtensions(this.update, this, this.disposables); - this.update(); + this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this.updateRunningExtensions(); } - update(): Promise { - return this.throttler.queue(() => { - this.enabled = false; - this.tooltip = ''; - if (!this.extension) { - return Promise.resolve(undefined); - } - const state = this.extension.state; - if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { - return Promise.resolve(undefined); - } - const installed = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; - const local = this.extension.local || (installed && installed.local); - if (local && local.manifest && local.manifest.contributes && local.manifest.contributes.localizations && local.manifest.contributes.localizations.length > 0) { - return Promise.resolve(undefined); - } - return this.extensionService.getExtensions() - .then(runningExtensions => this.computeReloadState(runningExtensions, installed)); - }).then(() => { - this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass; - }); + private updateRunningExtensions(): void { + this.extensionService.getExtensions().then(runningExtensions => this.runningExtensions = runningExtensions); } - private computeReloadState(runningExtensions: IExtensionDescription[], installed: IExtension): void { + update(): void { + this.enabled = false; + this.tooltip = ''; + if (!this.extension) { + return; + } + const state = this.extension.state; + if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { + return; + } + const installed = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; + const local = this.extension.local || (installed && installed.local); + if (local && local.manifest && local.manifest.contributes && local.manifest.contributes.localizations && local.manifest.contributes.localizations.length > 0) { + return; + } + this.computeReloadState(installed); + this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass; + } + + private computeReloadState(installed: IExtension): void { const isUninstalled = this.extension.state === ExtensionState.Uninstalled; const isDisabled = this.extension.local ? !this.extensionEnablementService.isEnabled(this.extension.local) : false; const isEnabled = this.extension.local ? this.extensionEnablementService.isEnabled(this.extension.local) : false; - const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; + const runningExtension = this.runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; if (installed && installed.local) { if (runningExtension) { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 41efc1c3b0..1d8e043978 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -11,12 +11,12 @@ import * as ExtensionsActions from 'vs/workbench/contrib/extensions/electron-bro import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; -import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; +import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -74,7 +74,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); instantiationService.stub(IRemoteAgentService, RemoteAgentService); - instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService, { authority: 'vscode-local', extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local' })); + instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService)); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1202,7 +1202,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Update, local: aLocalExtension('a', gallery, gallery) }); assert.ok(!testObject.enabled); }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 989c4f7785..fee73f8b7d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -34,7 +34,7 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; +import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/node/product'; import { ITextModel } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index a3ff8b2e7c..d75db0ac0a 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -12,12 +12,12 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, IQueryOptions, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, IExtensionManagementServerService, IExtensionManagementServer, EnablementState, ExtensionRecommendationReason, SortBy + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, IExtensionManagementServerService, EnablementState, ExtensionRecommendationReason, SortBy } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; -import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; +import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; import { Emitter } from 'vs/base/common/event'; @@ -90,7 +90,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); instantiationService.stub(IRemoteAgentService, RemoteAgentService); - instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService, { authority: 'vscode-local', extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local' })); + instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService)); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index d0a14fb49e..b4f042408a 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -12,12 +12,12 @@ import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurat import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; -import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; +import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -77,7 +77,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); instantiationService.stub(IRemoteAgentService, RemoteAgentService); - instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService, { authority: 'vscode-local', extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local' })); + instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService)); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index d085192756..2daf9b3332 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -94,7 +94,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut distinct( coalesce(this.editorService.visibleEditors .map(editorInput => { - const resource = toResource(editorInput, { supportSideBySide: true }); + const resource = toResource(editorInput, { supportSideBySide: SideBySideEditorChoice.MASTER }); return resource ? this.textFileService.models.get(resource) : undefined; })) .filter(model => !model.isDirty()), @@ -325,7 +325,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleUpdatesToVisibleBinaryEditors(e: FileChangesEvent): void { const editors = this.editorService.visibleControls; editors.forEach(editor => { - const resource = editor.input ? toResource(editor.input, { supportSideBySide: true }) : undefined; + const resource = editor.input ? toResource(editor.input, { supportSideBySide: SideBySideEditorChoice.MASTER }) : undefined; // Support side-by-side binary editors too let isBinaryEditor = false; @@ -346,7 +346,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleOutOfWorkspaceWatchers(): void { const visibleOutOfWorkspacePaths = new ResourceMap(); coalesce(this.editorService.visibleEditors.map(editorInput => { - return toResource(editorInput, { supportSideBySide: true }); + return toResource(editorInput, { supportSideBySide: SideBySideEditorChoice.MASTER }); })).filter(resource => { return this.fileService.canHandleResource(resource) && !this.contextService.isInsideWorkspace(resource); }).forEach(resource => { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index ec41105911..17184f2c09 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -17,7 +17,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files'; -import { toResource, ITextEditor } from 'vs/workbench/common/editor'; +import { toResource, ITextEditor, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -723,7 +723,7 @@ export class ShowActiveFileInExplorer extends Action { } public run(): Promise { - const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); } else { @@ -795,7 +795,7 @@ export class ShowOpenedFileInNewWindow extends Action { } public run(): Promise { - const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { this.windowService.openWindow([{ fileUri: fileResource }], { forceNewWindow: true }); @@ -898,7 +898,7 @@ export class CompareWithClipboardAction extends Action { } public run(): Promise { - const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { const provider = this.instantiationService.createInstance(ClipboardContentProvider); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index c2ce3c514f..05f0102f86 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; // {{SQL CARBON EDIT}} - Import EditorInput -import { toResource, IEditorCommandsContext, EditorInput } from 'vs/workbench/common/editor'; +import { toResource, IEditorCommandsContext, EditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -41,6 +41,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { ILabelService } from 'vs/platform/label/common/label'; import { onUnexpectedError } from 'vs/base/common/errors'; import { basename } from 'vs/base/common/resources'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; // {{SQL CARBON EDIT}} import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; @@ -136,7 +137,7 @@ function save( let viewStateOfSource: IEditorViewState | null; const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor, { supportSideBySide: true }); + const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { viewStateOfSource = activeTextEditorWidget.saveViewState(); } @@ -314,28 +315,46 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); const COMPARE_WITH_SAVED_SCHEMA = 'showModifications'; -let provider: FileOnDiskContentProvider; +let providerDisposables: IDisposable[] = []; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: COMPARE_WITH_SAVED_COMMAND_ID, when: undefined, weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D), handler: (accessor, resource: URI | object) => { - if (!provider) { - const instantiationService = accessor.get(IInstantiationService); - const textModelService = accessor.get(ITextModelService); - provider = instantiationService.createInstance(FileOnDiskContentProvider); - textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider); + const instantiationService = accessor.get(IInstantiationService); + const textModelService = accessor.get(ITextModelService); + const editorService = accessor.get(IEditorService); + + // Register provider at first as needed + let registerEditorListener = false; + if (providerDisposables.length === 0) { + registerEditorListener = true; + + const provider = instantiationService.createInstance(FileOnDiskContentProvider); + providerDisposables.push(provider); + providerDisposables.push(textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider)); } - const editorService = accessor.get(IEditorService); + // Open editor (only files supported) const uri = getResourceForCommand(resource, accessor.get(IListService), editorService); - if (uri && uri.scheme === Schemas.file /* only files on disk supported for now */) { const name = basename(uri); const editorLabel = nls.localize('modifiedLabel', "{0} (on disk) ↔ {1}", name, name); - return editorService.openEditor({ leftResource: uri.with({ scheme: COMPARE_WITH_SAVED_SCHEMA }), rightResource: uri, label: editorLabel }).then(() => undefined); + editorService.openEditor({ leftResource: uri.with({ scheme: COMPARE_WITH_SAVED_SCHEMA }), rightResource: uri, label: editorLabel }).then(() => { + + // Dispose once no more diff editor is opened with the scheme + if (registerEditorListener) { + providerDisposables.push(editorService.onDidVisibleEditorsChange(() => { + if (!editorService.editors.some(editor => !!toResource(editor, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: COMPARE_WITH_SAVED_SCHEMA }))) { + providerDisposables = dispose(providerDisposables); + } + })); + } + }, error => { + providerDisposables = dispose(providerDisposables); + }); } return Promise.resolve(true); @@ -554,7 +573,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const editorService = accessor.get(IEditorService); - const resource = toResource(editorService.activeEditor, { supportSideBySide: true }); + const resource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { // {{SQL CARBON EDIT}} return save(resource, false, { skipSaveParticipants: true }, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService)); @@ -586,7 +605,7 @@ CommandsRegistry.registerCommand({ const editorGroup = editorGroupService.getGroup(context.groupId); if (editorGroup) { editorGroup.editors.forEach(editor => { - const resource = toResource(editor, { supportSideBySide: true }); + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && (resource.scheme === Schemas.untitled || fileService.canHandleResource(resource))) { saveAllArg.push(resource); } diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 078704fc99..e5018b2535 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { OpenEditor } from 'vs/workbench/contrib/files/common/files'; -import { toResource } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -41,7 +41,7 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe } } - return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: true }) : null; + return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; } export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index d53674a16a..7bd605a807 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -25,9 +25,9 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider { this.toDispose.push(contextService.onDidChangeWorkspaceFolders(e => { this._onDidChange.fire(e.changed.concat(e.added).map(wf => wf.uri)); })); - this.toDispose.push(explorerService.onDidChangeItem(item => { - if (item) { - this._onDidChange.fire([item.resource]); + this.toDispose.push(explorerService.onDidChangeItem(change => { + if (change.item) { + this._onDidChange.fire([change.item.resource]); } })); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index a349f88803..53b277e4db 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -10,7 +10,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files'; import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; -import { toResource } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as DOM from 'vs/base/browser/dom'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; @@ -167,11 +167,11 @@ export class ExplorerView extends ViewletPanel { this.disposables.push(this.labelService.onDidChangeFormatters(() => { this._onDidChangeTitleArea.fire(); - this.refresh(); + this.refresh(true); })); this.disposables.push(this.explorerService.onDidChangeRoots(() => this.setTreeInput())); - this.disposables.push(this.explorerService.onDidChangeItem(e => this.refresh(e))); + this.disposables.push(this.explorerService.onDidChangeItem(e => this.refresh(e.recursive, e.item))); this.disposables.push(this.explorerService.onDidChangeEditable(async e => { const isEditing = !!this.explorerService.getEditableData(e); @@ -181,7 +181,7 @@ export class ExplorerView extends ViewletPanel { DOM.removeClass(treeContainer, 'highlight'); } - await this.refresh(e.parent); + await this.refresh(false, e.parent); if (isEditing) { DOM.addClass(treeContainer, 'highlight'); @@ -365,7 +365,7 @@ export class ExplorerView extends ViewletPanel { // Refresh viewer as needed if this originates from a config event if (event && needsRefresh) { - this.refresh(); + this.refresh(true); } } @@ -421,7 +421,7 @@ export class ExplorerView extends ViewletPanel { * Refresh the contents of the explorer to get up to date data from the disk about the file structure. * If the item is passed we refresh only that level of the tree, otherwise we do a full refresh. */ - private refresh(item?: ExplorerItem): Promise { + private refresh(recursive: boolean, item?: ExplorerItem): Promise { if (!this.tree || !this.isBodyVisible()) { this.shouldRefresh = true; return Promise.resolve(undefined); @@ -432,7 +432,6 @@ export class ExplorerView extends ViewletPanel { return Promise.resolve(undefined); } - const recursive = !item; const toRefresh = item || this.tree.getInput(); return this.tree.updateChildren(toRefresh, recursive); @@ -504,7 +503,7 @@ export class ExplorerView extends ViewletPanel { } // check for files - return withNullAsUndefined(toResource(input, { supportSideBySide: true })); + return withNullAsUndefined(toResource(input, { supportSideBySide: SideBySideEditor.MASTER })); } private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal): Promise { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index f66537ac4e..dd2f9062f6 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -325,8 +325,7 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); - if (cached && cached.parsed(path.normalize(path.relative(stat.root.resource.path, stat.resource.path)), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) { - // review (isidor): is path.normalize necessary? path.relative already returns an os path + if (cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) { return false; // hidden through pattern } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index b7ed79cc89..ad8e439982 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -32,7 +32,7 @@ export class ExplorerService implements IExplorerService { private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first private _onDidChangeRoots = new Emitter(); - private _onDidChangeItem = new Emitter(); + private _onDidChangeItem = new Emitter<{ item?: ExplorerItem, recursive: boolean }>(); private _onDidChangeEditable = new Emitter(); private _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>(); private _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>(); @@ -40,6 +40,7 @@ export class ExplorerService implements IExplorerService { private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; + private fileSystemProviderSchemes = new Set(); constructor( @IFileService private fileService: IFileService, @@ -60,7 +61,7 @@ export class ExplorerService implements IExplorerService { return this._onDidChangeRoots.event; } - get onDidChangeItem(): Event { + get onDidChangeItem(): Event<{ item?: ExplorerItem, recursive: boolean }> { return this._onDidChangeItem.event; } @@ -98,7 +99,14 @@ export class ExplorerService implements IExplorerService { this.disposables.push(this.fileService.onAfterOperation(e => this.onFileOperation(e))); this.disposables.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(() => this._onDidChangeItem.fire(undefined))); + this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) { + // A file system provider got re-registered, we should update all file stats since they might change (got read-only) + this._onDidChangeItem.fire({ recursive: true }); + } else { + this.fileSystemProviderSchemes.add(e.scheme); + } + })); this.disposables.push(model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); return model; @@ -158,19 +166,19 @@ export class ExplorerService implements IExplorerService { // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); - this._onDidChangeItem.fire(item ? item.parent : undefined); + this._onDidChangeItem.fire({ item: root, recursive: true }); // Select and Reveal this._onDidSelectResource.fire({ resource: item ? item.resource : undefined, reveal }); }, () => { root.isError = true; - this._onDidChangeItem.fire(root); + this._onDidChangeItem.fire({ item: root, recursive: false }); }); } refresh(): void { this.model.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire(undefined); + this._onDidChangeItem.fire({ recursive: true }); const resource = this.editorService.activeEditor ? this.editorService.activeEditor.getResource() : undefined; if (resource) { // We did a top level refresh, reveal the active file #67118 @@ -205,7 +213,7 @@ export class ExplorerService implements IExplorerService { p.removeChild(childElement); p.addChild(childElement); // Refresh the Parent (View) - this._onDidChangeItem.fire(p); + this._onDidChangeItem.fire({ item: p, recursive: false }); }); }); } @@ -224,7 +232,7 @@ export class ExplorerService implements IExplorerService { modelElements.forEach(modelElement => { // Rename File (Model) modelElement.rename(newElement); - this._onDidChangeItem.fire(modelElement.parent); + this._onDidChangeItem.fire({ item: modelElement.parent, recursive: false }); }); } @@ -238,8 +246,8 @@ export class ExplorerService implements IExplorerService { modelElements.forEach((modelElement, index) => { const oldParent = modelElement.parent; modelElement.move(newParents[index]); - this._onDidChangeItem.fire(oldParent); - this._onDidChangeItem.fire(newParents[index]); + this._onDidChangeItem.fire({ item: oldParent, recursive: false }); + this._onDidChangeItem.fire({ item: newParents[index], recursive: false }); }); } } @@ -254,7 +262,7 @@ export class ExplorerService implements IExplorerService { // Remove Element from Parent (Model) parent.removeChild(element); // Refresh Parent (View) - this._onDidChangeItem.fire(parent); + this._onDidChangeItem.fire({ item: parent, recursive: false }); } }); } @@ -332,7 +340,7 @@ export class ExplorerService implements IExplorerService { if (shouldRefresh()) { this.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire(undefined); + this._onDidChangeItem.fire({ recursive: true }); } }, ExplorerService.EXPLORER_FILE_CHANGES_REACT_DELAY); } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index fb9995f432..03783ec381 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorInput, toResource } from 'vs/workbench/common/editor'; +import { IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IFilesConfiguration, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; @@ -43,7 +43,7 @@ export interface IExplorerService { readonly roots: ExplorerItem[]; readonly sortOrder: SortOrder; readonly onDidChangeRoots: Event; - readonly onDidChangeItem: Event; + readonly onDidChangeItem: Event<{ item?: ExplorerItem, recursive: boolean }>; readonly onDidChangeEditable: Event; readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>; readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>; @@ -231,7 +231,7 @@ export class OpenEditor implements IEditorIdentifier { } public isUntitled(): boolean { - return !!toResource(this.editor, { supportSideBySide: true, filter: Schemas.untitled }); + return !!toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.untitled }); } public isDirty(): boolean { @@ -239,6 +239,6 @@ export class OpenEditor implements IEditorIdentifier { } public getResource(): URI | null { - return toResource(this.editor, { supportSideBySide: true }); + return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); } } diff --git a/src/vs/workbench/contrib/output/common/outputLinkProvider.ts b/src/vs/workbench/contrib/output/common/outputLinkProvider.ts index d8f7b6d4a3..223091eccf 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkProvider.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkProvider.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { LinkProviderRegistry, ILink } from 'vs/editor/common/modes'; +import { LinkProviderRegistry, ILink, ILinksList } from 'vs/editor/common/modes'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/contrib/output/common/output'; import { MonacoWebWorker, createWebWorker } from 'vs/editor/common/services/webWorker'; @@ -42,8 +42,8 @@ export class OutputLinkProvider { if (folders.length > 0) { if (!this.linkProviderRegistration) { this.linkProviderRegistration = LinkProviderRegistry.register([{ language: OUTPUT_MODE_ID, scheme: '*' }, { language: LOG_MODE_ID, scheme: '*' }], { - provideLinks: (model, token): Promise => { - return this.provideLinks(model.uri); + provideLinks: (model): Promise => { + return this.provideLinks(model.uri).then(links => links && { links }); } }); } diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts index cf49453284..3391de2a52 100644 --- a/src/vs/workbench/contrib/search/common/search.ts +++ b/src/vs/workbench/contrib/search/common/search.ts @@ -9,7 +9,7 @@ import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workben import { SymbolKind, Location, ProviderResult } from 'vs/editor/common/modes'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; -import { toResource } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -85,7 +85,7 @@ export function getOutOfWorkspaceEditorResources(editorService: IEditorService, const resources: URI[] = []; editorService.editors.forEach(editor => { - const resource = toResource(editor, { supportSideBySide: true }); + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && !contextService.isInsideWorkspace(resource)) { resources.push(resource); } diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index 50af7229e3..ee1074c331 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -241,7 +241,7 @@ CommandsRegistry.registerCommand('_workbench.captureSyntaxTokens', function (acc if (!resource) { const editorService = accessor.get(IEditorService); - const file = editorService.activeEditor ? toResource(editorService.activeEditor, { filter: 'file' }) : null; + const file = editorService.activeEditor ? toResource(editorService.activeEditor, { filterByScheme: 'file' }) : null; if (file) { process(file).then(result => { console.log(result); diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 9e5fda5f0f..277775e502 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -41,9 +41,9 @@ const defaultCssRules = ` body { background-color: var(--vscode-editor-background); color: var(--vscode-editor-foreground); - font-family: var(--vscode-editor-font-family); - font-weight: var(--vscode-editor-font-weight); - font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-font-family); + font-weight: var(--vscode-font-weight); + font-size: var(--vscode-font-size); margin: 0; padding: 0 20px; } @@ -112,16 +112,18 @@ module.exports = function createWebviewManager(host) { }; /** - * @param {HTMLDocument} document - * @param {HTMLElement} body + * @param {HTMLDocument?} document + * @param {HTMLElement?} body */ const applyStyles = (document, body) => { - if (!body) { + if (!document) { return; } - body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); - body.classList.add(initData.activeTheme); + if (body) { + body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); + body.classList.add(initData.activeTheme); + } if (initData.styles) { for (const variable of Object.keys(initData.styles)) { @@ -323,7 +325,16 @@ module.exports = function createWebviewManager(host) { // write new content onto iframe newFrame.contentDocument.open('text/html', 'replace'); + newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); + + newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { + const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; + if (contentDocument) { + applyStyles(contentDocument, contentDocument.body); + } + }); + newFrame.contentWindow.onbeforeunload = () => { if (isInDevelopmentMode) { // Allow reloads while developing a webview host.postMessage('do-reload'); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 767536d6fe..580c60220c 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -22,6 +22,8 @@ import { WebviewFindWidget } from '../browser/webviewFindWidget'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { WebviewContentOptions, WebviewPortMapping, WebviewOptions, Webview } from 'vs/workbench/contrib/webview/common/webview'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; interface IKeydownEvent { @@ -306,7 +308,6 @@ export class WebviewElement extends Disposable implements Webview { public get onDidFocus(): Event { return this._onDidFocus.event; } constructor( - private readonly _styleElement: Element, private readonly _options: WebviewOptions, private _contentOptions: WebviewContentOptions, @IInstantiationService instantiationService: IInstantiationService, @@ -314,6 +315,7 @@ export class WebviewElement extends Disposable implements Webview { @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @ITelemetryService telemetryService: ITelemetryService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); this._webview = document.createElement('webview'); @@ -540,9 +542,12 @@ export class WebviewElement extends Disposable implements Webview { }); } - // {{SQL CARBON EDIT}} + // {{SQL CARBON EDIT}} - make public public style(theme: ITheme): void { - const { fontFamily, fontWeight, fontSize } = window.getComputedStyle(this._styleElement); // TODO@theme avoid styleElement + const configuration = this._configurationService.getValue('editor'); + const editorFontFamily = configuration.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; + const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight; + const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize; const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => { const color = theme.getColor(entry.id); @@ -554,9 +559,12 @@ export class WebviewElement extends Disposable implements Webview { const styles = { - 'vscode-editor-font-family': fontFamily, - 'vscode-editor-font-weight': fontWeight, - 'vscode-editor-font-size': fontSize, + 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', + 'vscode-font-weight': 'normal', + 'vscode-font-size': '13px', + 'vscode-editor-font-family': editorFontFamily, + 'vscode-editor-font-weight': editorFontWeight, + 'vscode-editor-font-size': editorFontSize, ...exportedColors }; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index cebf24373f..e341b1864e 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -4,15 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { IWebviewService, WebviewOptions, WebviewContentOptions, Webview } from 'vs/workbench/contrib/webview/common/webview'; export class WebviewService implements IWebviewService { _serviceBrand: any; constructor( - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { } @@ -21,7 +19,6 @@ export class WebviewService implements IWebviewService { contentOptions: WebviewContentOptions ): Webview { const element = this._instantiationService.createInstance(WebviewElement, - this._layoutService.getContainer(Parts.EDITOR_PART), options, contentOptions); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index f42cb63ee7..0f0eca3784 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -168,6 +168,11 @@ class CodeRendererMain extends Disposable { private initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: StorageService }> { const serviceCollection = new ServiceCollection(); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. + // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Main Process const mainProcessService = this._register(new MainProcessService(this.configuration.windowId)); serviceCollection.set(IMainProcessService, mainProcessService); @@ -205,7 +210,7 @@ class CodeRendererMain extends Disposable { } return this.resolveWorkspaceInitializationPayload(environmentService).then(payload => Promise.all([ - this.createWorkspaceService(payload, environmentService, remoteAgentService, logService).then(service => { + this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => { // Workspace serviceCollection.set(IWorkspaceContextService, service); @@ -296,8 +301,11 @@ class CodeRendererMain extends Disposable { }, error => onUnexpectedError(error)); } - private createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService); + private createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, fileService: FileService2, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { + const configurationFileService = new ConfigurationFileService(); + fileService.whenReady.then(() => configurationFileService.fileService = fileService); + + const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); return workspaceService.initialize(payload).then(() => workspaceService, error => { onUnexpectedError(error); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index c3c979bcb3..00ca4f27e5 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -11,7 +11,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, Action } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { toResource, IUntitledResourceInput, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IPathData, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows'; @@ -122,7 +122,7 @@ export class ElectronWindow extends Disposable { if (request.from === 'touchbar') { const activeEditor = this.editorService.activeEditor; if (activeEditor) { - const resource = toResource(activeEditor, { supportSideBySide: true }); + const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { args.push(resource); } diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 661143523e..8a4a16d6d7 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; @@ -22,67 +22,36 @@ import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration'; import { createSHA1 } from 'vs/base/browser/hash'; - -export class LocalUserConfiguration extends Disposable { - - private readonly userConfigurationResource: URI; - private userConfiguration: NodeBasedUserConfiguration | FileServiceBasedUserConfiguration; - private changeDisposable: IDisposable = Disposable.None; - - private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - - constructor( - userConfigurationResource: URI, - configurationFileService: IConfigurationFileService - ) { - super(); - this.userConfigurationResource = userConfigurationResource; - this.userConfiguration = this._register(new NodeBasedUserConfiguration(this.userConfigurationResource, configurationFileService)); - } - - initialize(): Promise { - return this.userConfiguration.initialize(); - } - - reload(): Promise { - return this.userConfiguration.reload(); - } - - async adopt(fileService: IFileService): Promise { - if (this.userConfiguration instanceof NodeBasedUserConfiguration) { - const oldConfigurationModel = this.userConfiguration.getConfigurationModel(); - this.userConfiguration.dispose(); - dispose(this.changeDisposable); - - let newConfigurationModel = new ConfigurationModel(); - this.userConfiguration = this._register(new FileServiceBasedUserConfiguration(this.userConfigurationResource, fileService)); - this.changeDisposable = this._register(this.userConfiguration.onDidChangeConfiguration(configurationModel => this._onDidChangeConfiguration.fire(configurationModel))); - newConfigurationModel = await this.userConfiguration.initialize(); - - const { added, updated, removed } = compare(oldConfigurationModel, newConfigurationModel); - if (added.length > 0 || updated.length > 0 || removed.length > 0) { - return newConfigurationModel; - } - } - return null; - } -} +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedUserConfiguration; - private _userConfiguration: FileServiceBasedUserConfiguration | CachedUserConfiguration; + private readonly _configurationFileService: IConfigurationFileService; + private _userConfiguration: UserConfiguration | CachedUserConfiguration; + private _userConfigurationDisposable: IDisposable = Disposable.None; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor( remoteAuthority: string, - configurationCache: IConfigurationCache + configurationCache: IConfigurationCache, + configurationFileService: IConfigurationFileService, + remoteAgentService: IRemoteAgentService ) { super(); + this._configurationFileService = configurationFileService; this._userConfiguration = this._cachedConfiguration = new CachedUserConfiguration(remoteAuthority, configurationCache); + remoteAgentService.getEnvironment().then(environment => { + if (environment) { + this._userConfiguration.dispose(); + this._userConfigurationDisposable.dispose(); + this._userConfiguration = this._register(new UserConfiguration(environment.appSettingsPath, this._configurationFileService)); + this._userConfigurationDisposable = this._register(this._userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); + this._userConfiguration.initialize().then(configurationModel => this.onDidUserConfigurationChange(configurationModel)); + } + }); } initialize(): Promise { @@ -93,24 +62,6 @@ export class RemoteUserConfiguration extends Disposable { return this._userConfiguration.reload(); } - async adopt(configurationResource: URI | null, fileService: IFileService): Promise { - if (this._userConfiguration instanceof CachedUserConfiguration) { - const oldConfigurationModel = this._userConfiguration.getConfigurationModel(); - let newConfigurationModel = new ConfigurationModel(); - if (configurationResource) { - this._userConfiguration = new FileServiceBasedUserConfiguration(configurationResource, fileService); - this._register(this._userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); - newConfigurationModel = await this._userConfiguration.initialize(); - } - const { added, updated, removed } = compare(oldConfigurationModel, newConfigurationModel); - if (added.length > 0 || updated.length > 0 || removed.length > 0) { - this.updateCache(newConfigurationModel); - return newConfigurationModel; - } - } - return null; - } - private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void { this.updateCache(configurationModel); this._onDidChangeConfiguration.fire(configurationModel); @@ -121,51 +72,7 @@ export class RemoteUserConfiguration extends Disposable { } } -class NodeBasedUserConfiguration extends Disposable { - - private configuraitonModel: ConfigurationModel = new ConfigurationModel(); - - constructor( - private readonly userConfigurationResource: URI, - private readonly configurationFileService: IConfigurationFileService - ) { - super(); - } - - initialize(): Promise { - return this._load(); - } - - reload(): Promise { - return this._load(); - } - - getConfigurationModel(): ConfigurationModel { - return this.configuraitonModel; - } - - async _load(): Promise { - const exists = await this.configurationFileService.exists(this.userConfigurationResource); - if (exists) { - try { - const content = await this.configurationFileService.resolveContent(this.userConfigurationResource); - const parser = new ConfigurationModelParser(this.userConfigurationResource.toString()); - parser.parse(content); - this.configuraitonModel = parser.configurationModel; - } catch (e) { - // ignore error - errors.onUnexpectedError(e); - this.configuraitonModel = new ConfigurationModel(); - } - } else { - this.configuraitonModel = new ConfigurationModel(); - } - return this.configuraitonModel; - } - -} - -export class FileServiceBasedUserConfiguration extends Disposable { +export class UserConfiguration extends Disposable { private readonly reloadConfigurationScheduler: RunOnceScheduler; protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -176,11 +83,11 @@ export class FileServiceBasedUserConfiguration extends Disposable { constructor( private readonly configurationResource: URI, - private readonly fileService: IFileService + private readonly configurationFileService: IConfigurationFileService ) { super(); - this._register(fileService.onFileChanges(e => this.handleFileEvents(e))); + this._register(configurationFileService.onFileChanges(e => this.handleFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); this._register(toDisposable(() => { this.stopWatchingResource(); @@ -189,7 +96,7 @@ export class FileServiceBasedUserConfiguration extends Disposable { } private watchResource(): void { - this.fileWatcherDisposable = this.fileService.watch(this.configurationResource); + this.fileWatcherDisposable = this.configurationFileService.watch(this.configurationResource); } private stopWatchingResource(): void { @@ -199,7 +106,7 @@ export class FileServiceBasedUserConfiguration extends Disposable { private watchDirectory(): void { const directory = resources.dirname(this.configurationResource); - this.directoryWatcherDisposable = this.fileService.watch(directory); + this.directoryWatcherDisposable = this.configurationFileService.watch(directory); } private stopWatchingDirectory(): void { @@ -208,22 +115,34 @@ export class FileServiceBasedUserConfiguration extends Disposable { } async initialize(): Promise { - const exists = await this.fileService.exists(this.configurationResource); + const exists = await this.configurationFileService.exists(this.configurationResource); this.onResourceExists(exists); - return this.reload(); + const configuraitonModel = await this.reload(); + if (!this.configurationFileService.isWatching) { + this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted(configuraitonModel)); + } + return configuraitonModel; } async reload(): Promise { try { - const content = await this.fileService.resolveContent(this.configurationResource); + const content = await this.configurationFileService.resolveContent(this.configurationResource); const parser = new ConfigurationModelParser(this.configurationResource.toString()); - parser.parse(content.value); + parser.parse(content); return parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } + private async onWatchStarted(currentModel: ConfigurationModel): Promise { + const configuraitonModel = await this.reload(); + const { added, removed, updated } = compare(currentModel, configuraitonModel); + if (added.length || removed.length || updated.length) { + this._onDidChangeConfiguration.fire(configuraitonModel); + } + } + private async handleFileEvents(event: FileChangesEvent): Promise { const events = event.changes; @@ -304,41 +223,38 @@ class CachedUserConfiguration extends Disposable { export class WorkspaceConfiguration extends Disposable { - private readonly _cachedConfiguration: CachedWorkspaceConfiguration; private readonly _configurationFileService: IConfigurationFileService; + private readonly _cachedConfiguration: CachedWorkspaceConfiguration; private _workspaceConfiguration: IWorkspaceConfiguration; + private _workspaceConfigurationChangeDisposable: IDisposable = Disposable.None; private _workspaceIdentifier: IWorkspaceIdentifier | null = null; - private _fileService: IFileService | null = null; private readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private _loaded: boolean = false; + get loaded(): boolean { return this._loaded; } + constructor( configurationCache: IConfigurationCache, configurationFileService: IConfigurationFileService ) { super(); - this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache); this._configurationFileService = configurationFileService; - this._workspaceConfiguration = this._cachedConfiguration; + this._workspaceConfiguration = this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache); } - adopt(fileService: IFileService): Promise { - if (!this._fileService) { - this._fileService = fileService; - if (this.adoptWorkspaceConfiguration()) { - if (this._workspaceIdentifier) { - return this._workspaceConfiguration.load(this._workspaceIdentifier).then(() => true); - } + async load(workspaceIdentifier: IWorkspaceIdentifier): Promise { + this._workspaceIdentifier = workspaceIdentifier; + if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { + if (this._workspaceIdentifier.configPath.scheme === Schemas.file) { + this.switch(); + } else { + this.waitAndSwitch(this._workspaceIdentifier); } } - return Promise.resolve(false); - } - - load(workspaceIdentifier: IWorkspaceIdentifier): Promise { - this._workspaceIdentifier = workspaceIdentifier; - this.adoptWorkspaceConfiguration(); - return this._workspaceConfiguration.load(this._workspaceIdentifier); + await this._workspaceConfiguration.load(this._workspaceIdentifier); + this._loaded = this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration; } reload(): Promise { @@ -366,38 +282,26 @@ export class WorkspaceConfiguration extends Disposable { return this.getConfiguration(); } - private adoptWorkspaceConfiguration(): boolean { - if (this._workspaceIdentifier) { - if (this._fileService) { - if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { - const oldWorkspaceConfiguration = this._workspaceConfiguration; - this._workspaceConfiguration = new FileServiceBasedWorkspaceConfiguration(this._fileService, oldWorkspaceConfiguration); - this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange())); - if (oldWorkspaceConfiguration instanceof CachedWorkspaceConfiguration) { - this.updateCache(); - return true; - } else { - dispose(oldWorkspaceConfiguration); - return false; - } - } - return false; - } - if (this._workspaceIdentifier.configPath.scheme === Schemas.file) { - if (!(this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration)) { - dispose(this._workspaceConfiguration); - this._workspaceConfiguration = new NodeBasedWorkspaceConfiguration(this._configurationFileService); - return true; - } - return false; - } + private async waitAndSwitch(workspaceIdentifier: IWorkspaceIdentifier): Promise { + await this._configurationFileService.whenProviderRegistered(workspaceIdentifier.configPath.scheme); + if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { + this.switch(); + this._loaded = true; + this.onDidWorkspaceConfigurationChange(); } - return false; } - private onDidWorkspaceConfigurationChange(): void { + private switch(): void { + this._workspaceConfiguration.dispose(); + this._workspaceConfigurationChangeDisposable.dispose(); + this._workspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); + this._workspaceConfigurationChangeDisposable = this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange())); + } + + private async onDidWorkspaceConfigurationChange(): Promise { + await this.reload(); this.updateCache(); - this.reload().then(() => this._onDidUpdateConfiguration.fire()); + this._onDidUpdateConfiguration.fire(); } private updateCache(): Promise { @@ -421,20 +325,30 @@ interface IWorkspaceConfiguration extends IDisposable { reprocessWorkspaceSettings(): ConfigurationModel; } -abstract class AbstractWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration { +class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration { workspaceConfigurationModelParser: WorkspaceConfigurationModelParser; workspaceSettings: ConfigurationModel; private _workspaceIdentifier: IWorkspaceIdentifier | null = null; + private workspaceConfigWatcher: IDisposable; + private readonly reloadConfigurationScheduler: RunOnceScheduler; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(from?: IWorkspaceConfiguration) { + constructor(private configurationFileService: IConfigurationFileService) { super(); - this.workspaceConfigurationModelParser = from ? from.workspaceConfigurationModelParser : new WorkspaceConfigurationModelParser(''); - this.workspaceSettings = from ? from.workspaceSettings : new ConfigurationModel(); + this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(''); + this.workspaceSettings = new ConfigurationModel(); + + this._register(configurationFileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); + this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); + this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile()); + + if (!this.configurationFileService.isWatching) { + this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted()); + } } get workspaceIdentifier(): IWorkspaceIdentifier | null { @@ -442,13 +356,20 @@ abstract class AbstractWorkspaceConfiguration extends Disposable implements IWor } async load(workspaceIdentifier: IWorkspaceIdentifier): Promise { - this._workspaceIdentifier = workspaceIdentifier; - this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(workspaceIdentifier.id); + if (!this._workspaceIdentifier || this._workspaceIdentifier.id !== workspaceIdentifier.id) { + this._workspaceIdentifier = workspaceIdentifier; + this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceIdentifier.id); + dispose(this.workspaceConfigWatcher); + this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile()); + } let contents = ''; try { - contents = (await this.loadWorkspaceConfigurationContents(workspaceIdentifier.configPath)) || ''; - } catch (e) { - errors.onUnexpectedError(e); + contents = await this.configurationFileService.resolveContent(this._workspaceIdentifier.configPath); + } catch (error) { + const exists = await this.configurationFileService.exists(this._workspaceIdentifier.configPath); + if (exists) { + errors.onUnexpectedError(error); + } } this.workspaceConfigurationModelParser.parse(contents); this.consolidate(); @@ -472,69 +393,34 @@ abstract class AbstractWorkspaceConfiguration extends Disposable implements IWor return this.getWorkspaceSettings(); } + private async onWatchStarted(): Promise { + if (this.workspaceIdentifier) { + const currentModel = this.getConfigurationModel(); + await this.load(this.workspaceIdentifier); + const newModel = this.getConfigurationModel(); + const { added, removed, updated } = compare(currentModel, newModel); + if (added.length || removed.length || updated.length) { + this._onDidChange.fire(); + } + } + } + private consolidate(): void { this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel); } - protected abstract loadWorkspaceConfigurationContents(workspaceConfigurationResource: URI): Promise; -} - -class NodeBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration { - - constructor(private readonly configurationFileService: IConfigurationFileService) { - super(); - } - - protected async loadWorkspaceConfigurationContents(workspaceConfigurationResource: URI): Promise { - const exists = await this.configurationFileService.exists(workspaceConfigurationResource); - if (exists) { - return this.configurationFileService.resolveContent(workspaceConfigurationResource); - } - return undefined; - } - -} - -class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration { - - private workspaceConfig: URI | null = null; - private workspaceConfigWatcher: IDisposable; - - private readonly reloadConfigurationScheduler: RunOnceScheduler; - - constructor(private fileService: IFileService, from?: IWorkspaceConfiguration) { - super(from); - this.workspaceConfig = from && from.workspaceIdentifier ? from.workspaceIdentifier.configPath : null; - this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); - this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); - this.workspaceConfigWatcher = this.watchWorkspaceConfigurationFile(); - } - private watchWorkspaceConfigurationFile(): IDisposable { - if (this.workspaceConfig) { - return this.fileService.watch(this.workspaceConfig); - } - - return Disposable.None; - } - - protected loadWorkspaceConfigurationContents(workspaceConfigurationResource: URI): Promise { - if (!(this.workspaceConfig && resources.isEqual(this.workspaceConfig, workspaceConfigurationResource))) { - dispose(this.workspaceConfigWatcher); - this.workspaceConfig = workspaceConfigurationResource; - this.workspaceConfigWatcher = this.watchWorkspaceConfigurationFile(); - } - return this.fileService.resolveContent(this.workspaceConfig).then(content => content.value); + return this._workspaceIdentifier ? this.configurationFileService.watch(this._workspaceIdentifier.configPath) : Disposable.None; } private handleWorkspaceFileEvents(event: FileChangesEvent): void { - if (this.workspaceConfig) { + if (this._workspaceIdentifier) { const events = event.changes; let affectedByChanges = false; // Find changes that affect workspace file for (let i = 0, len = events.length; i < len && !affectedByChanges; i++) { - affectedByChanges = resources.isEqual(this.workspaceConfig, events[i].resource); + affectedByChanges = resources.isEqual(this._workspaceIdentifier.configPath, events[i].resource); } if (affectedByChanges) { @@ -542,12 +428,6 @@ class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfigurat } } } - - dispose(): void { - super.dispose(); - - dispose(this.workspaceConfigWatcher); - } } class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration { @@ -617,45 +497,50 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi export interface IFolderConfiguration extends IDisposable { readonly onDidChange: Event; - readonly loaded: boolean; loadConfiguration(): Promise; reprocess(): ConfigurationModel; } -export abstract class AbstractFolderConfiguration extends Disposable implements IFolderConfiguration { +class FileServiceBasedFolderConfiguration extends Disposable implements IFolderConfiguration { private _folderSettingsModelParser: FolderSettingsModelParser; private _standAloneConfigurations: ConfigurationModel[]; private _cache: ConfigurationModel; - private _loaded: boolean = false; private readonly configurationNames: string[]; protected readonly configurationResources: URI[]; + private changeEventTriggerScheduler: RunOnceScheduler; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, from?: AbstractFolderConfiguration) { + constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, private configurationFileService: IConfigurationFileService) { super(); - this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY]; + this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY]; this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); - this._folderSettingsModelParser = from ? from._folderSettingsModelParser : new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? [ConfigurationScope.RESOURCE] : [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]); - this._standAloneConfigurations = from ? from._standAloneConfigurations : []; - this._cache = from ? from._cache : new ConfigurationModel(); - } + this._folderSettingsModelParser = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? [ConfigurationScope.RESOURCE] : [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]); + this._standAloneConfigurations = []; + this._cache = new ConfigurationModel(); - get loaded(): boolean { - return this._loaded; + this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); + this._register(configurationFileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); + if (!this.configurationFileService.isWatching) { + this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted()); + } } async loadConfiguration(): Promise { - const configurationContents = await Promise.all(this.configurationResources.map(resource => - this.loadConfigurationResourceContents(resource) - .then(undefined, error => { - /* never fail */ + const configurationContents = await Promise.all(this.configurationResources.map(async resource => { + try { + return await this.configurationFileService.resolveContent(resource); + } catch (error) { + const exists = await this.configurationFileService.exists(resource); + if (exists) { errors.onUnexpectedError(error); - return undefined; - }))); + } + } + return undefined; + })); // reset this._standAloneConfigurations = []; @@ -677,7 +562,6 @@ export abstract class AbstractFolderConfiguration extends Disposable implements // Consolidate (support *.json files in the workspace settings folder) this.consolidate(); - this._loaded = true; return this._cache; } @@ -694,41 +578,13 @@ export abstract class AbstractFolderConfiguration extends Disposable implements this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations); } - protected abstract loadConfigurationResourceContents(configurationResource: URI): Promise; -} - -export class NodeBasedFolderConfiguration extends AbstractFolderConfiguration { - - constructor(private readonly configurationFileService: IConfigurationFileService, configurationFolder: URI, workbenchState: WorkbenchState) { - super(configurationFolder, workbenchState); - } - - protected async loadConfigurationResourceContents(configurationResource: URI): Promise { - const exists = await this.configurationFileService.exists(configurationResource); - if (exists) { - return this.configurationFileService.resolveContent(configurationResource); + private async onWatchStarted(): Promise { + const currentModel = this._cache; + const newModel = await this.loadConfiguration(); + const { added, removed, updated } = compare(currentModel, newModel); + if (added.length || removed.length || updated.length) { + this._onDidChange.fire(); } - return undefined; - } -} - -export class FileServiceBasedFolderConfiguration extends AbstractFolderConfiguration { - - private changeEventTriggerScheduler: RunOnceScheduler; - - constructor(configurationFolder: URI, workbenchState: WorkbenchState, private fileService: IFileService, from?: AbstractFolderConfiguration) { - super(configurationFolder, workbenchState, from); - this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); - this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); - } - - protected async loadConfigurationResourceContents(configurationResource: URI): Promise { - const exists = await this.fileService.exists(configurationResource); - if (exists) { - const contents = await this.fileService.resolveContent(configurationResource); - return contents.value; - } - return undefined; } private handleWorkspaceFileEvents(event: FileChangesEvent): void { @@ -784,7 +640,6 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati private configurationModel: ConfigurationModel; private readonly key: Thenable; - loaded: boolean = false; constructor( folder: URI, @@ -802,7 +657,6 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati const contents = await this.configurationCache.read(key); const parsed: IConfigurationModel = JSON.parse(contents.toString()); this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides); - this.loaded = true; } catch (e) { } return this.configurationModel; @@ -832,79 +686,44 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat readonly onDidChange: Event = this._onDidChange.event; private folderConfiguration: IFolderConfiguration; + private folderConfigurationDisposable: IDisposable = Disposable.None; private readonly configurationFolder: URI; private cachedFolderConfiguration: CachedFolderConfiguration; - private _loaded: boolean = false; constructor( readonly workspaceFolder: IWorkspaceFolder, configFolderRelativePath: string, private readonly workbenchState: WorkbenchState, configurationFileService: IConfigurationFileService, - configurationCache: IConfigurationCache, - fileService?: IFileService + configurationCache: IConfigurationCache ) { super(); this.configurationFolder = resources.joinPath(workspaceFolder.uri, configFolderRelativePath); - this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache); - this.folderConfiguration = this.cachedFolderConfiguration; - if (fileService) { - this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); - } else if (workspaceFolder.uri.scheme === Schemas.file) { - this.folderConfiguration = new NodeBasedFolderConfiguration(configurationFileService, this.configurationFolder, this.workbenchState); + this.folderConfiguration = this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache); + if (workspaceFolder.uri.scheme === Schemas.file) { + this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, configurationFileService); + } else { + configurationFileService.whenProviderRegistered(workspaceFolder.uri.scheme) + .then(() => { + this.folderConfiguration.dispose(); + this.folderConfigurationDisposable.dispose(); + this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, configurationFileService); + this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); + this.onDidFolderConfigurationChange(); + }); } - this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); + this.folderConfigurationDisposable = this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); } loadConfiguration(): Promise { - return this.folderConfiguration.loadConfiguration() - .then(model => { - this._loaded = this.folderConfiguration.loaded; - return model; - }); + return this.folderConfiguration.loadConfiguration(); } reprocess(): ConfigurationModel { return this.folderConfiguration.reprocess(); } - get loaded(): boolean { - return this._loaded; - } - - adopt(fileService: IFileService): Promise { - if (fileService) { - if (this.folderConfiguration instanceof CachedFolderConfiguration) { - return this.adoptFromCachedConfiguration(fileService); - } - - if (this.folderConfiguration instanceof NodeBasedFolderConfiguration) { - return this.adoptFromNodeBasedConfiguration(fileService); - } - } - return Promise.resolve(false); - } - - private adoptFromCachedConfiguration(fileService: IFileService): Promise { - const folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); - return folderConfiguration.loadConfiguration() - .then(() => { - this.folderConfiguration = folderConfiguration; - this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); - this.updateCache(); - return true; - }); - } - - private adoptFromNodeBasedConfiguration(fileService: IFileService): Promise { - const oldFolderConfiguration = this.folderConfiguration; - this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService, oldFolderConfiguration); - oldFolderConfiguration.dispose(); - this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); - return Promise.resolve(false); - } - private onDidFolderConfigurationChange(): void { this.updateCache(); this._onDidChange.fire(); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 4bdcd95403..96eb1792e7 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -13,7 +13,6 @@ import { Queue, Barrier } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { isLinux } from 'vs/base/common/platform'; -import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; @@ -23,13 +22,12 @@ import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resour import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, LocalUserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; +import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { localize } from 'vs/nls'; import { isEqual, dirname } from 'vs/base/common/resources'; import { mark } from 'vs/base/common/performance'; -import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class WorkspaceService extends Disposable implements IConfigurationService, IWorkspaceContextService { @@ -41,7 +39,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; private defaultConfiguration: DefaultConfigurationModel; - private localUserConfiguration: LocalUserConfiguration | null = null; + private localUserConfiguration: UserConfiguration | null = null; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; private cachedFolderConfigs: ResourceMap; @@ -60,14 +58,13 @@ export class WorkspaceService extends Disposable implements IConfigurationServic protected readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkbenchState: Event = this._onDidChangeWorkbenchState.event; - private fileService: IFileService; private configurationEditingService: ConfigurationEditingService; private jsonEditingService: JSONEditingService; constructor( { userSettingsResource, remoteAuthority, configurationCache }: { userSettingsResource?: URI, remoteAuthority?: string, configurationCache: IConfigurationCache }, private readonly configurationFileService: IConfigurationFileService, - private readonly remoteAgentService: IRemoteAgentService, + remoteAgentService: IRemoteAgentService, ) { super(); @@ -75,11 +72,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.defaultConfiguration = new DefaultConfigurationModel(); this.configurationCache = configurationCache; if (userSettingsResource) { - this.localUserConfiguration = this._register(new LocalUserConfiguration(userSettingsResource, configurationFileService)); + this.localUserConfiguration = this._register(new UserConfiguration(userSettingsResource, configurationFileService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); } if (remoteAuthority) { - this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache)); + this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, configurationFileService, remoteAgentService)); this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration))); } this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, this.configurationFileService)); @@ -229,9 +226,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic // Workspace Configuration Service Impl getConfigurationData(): IConfigurationData { - const configurationData = this._configuration.toData(); - configurationData.isComplete = this.cachedFolderConfigs.values().every(c => c.loaded); - return configurationData; + return this._configuration.toData(); } getValue(): T; @@ -294,45 +289,6 @@ export class WorkspaceService extends Disposable implements IConfigurationServic }); } - acquireFileService(fileService: IFileService): void { - this.fileService = fileService; - const changedWorkspaceFolders: IWorkspaceFolder[] = []; - if (this.localUserConfiguration) { - this.localUserConfiguration.adopt(fileService) - .then(changedModel => { - if (changedModel) { - this.onLocalUserConfigurationChanged(changedModel); - } - }); - } - Promise.all([this.workspaceConfiguration.adopt(fileService), ...this.cachedFolderConfigs.values() - .map(folderConfiguration => folderConfiguration.adopt(fileService) - .then(result => { - if (result) { - changedWorkspaceFolders.push(folderConfiguration.workspaceFolder); - } - return result; - }))]) - .then(([workspaceChanged]) => { - if (workspaceChanged) { - this.onWorkspaceConfigurationChanged(); - } - for (const workspaceFolder of changedWorkspaceFolders) { - this.onWorkspaceFolderConfigurationChanged(workspaceFolder); - } - this.releaseWorkspaceBarrier(); - }); - if (this.remoteUserConfiguration) { - this.remoteAgentService.getEnvironment() - .then(environment => this.remoteUserConfiguration!.adopt(environment ? environment.appSettingsPath : null, fileService) - .then(changedModel => { - if (changedModel) { - this.onRemoteUserConfigurationChanged(changedModel); - } - })); - } - } - acquireInstantiationService(instantiationService: IInstantiationService): void { this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); this.jsonEditingService = instantiationService.createInstance(JSONEditingService); @@ -357,8 +313,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), dirname(workspaceConfigPath)); const workspaceId = workspaceIdentifier.id; const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath); - if (workspace.configuration!.scheme === Schemas.file) { - this.releaseWorkspaceBarrier(); // Release barrier as workspace is complete because it is from disk. + if (this.workspaceConfiguration.loaded) { + this.releaseWorkspaceBarrier(); } return workspace; }); @@ -583,6 +539,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE); } } + if (this.workspaceConfiguration.loaded) { + this.releaseWorkspaceBarrier(); + } return Promise.resolve(undefined); } @@ -629,7 +588,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return Promise.all([...folders.map(folder => { let folderConfiguration = this.cachedFolderConfigs.get(folder.uri); if (!folderConfiguration) { - folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.configurationFileService, this.configurationCache, this.fileService); + folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.configurationFileService, this.configurationCache); this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder))); this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); } diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 37e24ab32f..d83c61b662 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; export const FOLDER_CONFIG_FOLDER_NAME = '.azuredatastudio'; export const FOLDER_SETTINGS_NAME = 'settings'; @@ -34,6 +37,48 @@ export interface IConfigurationCache { } export interface IConfigurationFileService { + fileService: IFileService | null; + readonly onFileChanges: Event; + readonly isWatching: boolean; + readonly whenWatchingStarted: Promise; + whenProviderRegistered(scheme: string): Promise; + watch(resource: URI): IDisposable; exists(resource: URI): Promise; resolveContent(resource: URI): Promise; } + +export class ConfigurationFileService implements IConfigurationFileService { + + constructor(public fileService: IFileService) { } + + get onFileChanges() { return this.fileService.onFileChanges; } + readonly whenWatchingStarted: Promise = Promise.resolve(); + readonly isWatching: boolean = true; + + whenProviderRegistered(scheme: string): Promise { + if (this.fileService.canHandleResource(URI.from({ scheme }))) { + return Promise.resolve(); + } + return new Promise((c, e) => { + const disposable = this.fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (e.scheme === scheme && e.added) { + disposable.dispose(); + c(); + } + }); + }); + } + + watch(resource: URI): IDisposable { + return this.fileService.watch(resource); + } + + exists(resource: URI): Promise { + return this.fileService.exists(resource); + } + + resolveContent(resource: URI): Promise { + return this.fileService.resolveContent(resource, { encoding: 'utf8' }).then(content => content.value); + } + +} diff --git a/src/vs/workbench/services/configuration/node/configurationFileService.ts b/src/vs/workbench/services/configuration/node/configurationFileService.ts index ef6d1b6ea3..77bcc9cdbb 100644 --- a/src/vs/workbench/services/configuration/node/configurationFileService.ts +++ b/src/vs/workbench/services/configuration/node/configurationFileService.ts @@ -4,18 +4,76 @@ *--------------------------------------------------------------------------------------------*/ import * as pfs from 'vs/base/node/pfs'; -import { IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; +import { IConfigurationFileService, ConfigurationFileService as FileServiceBasedConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; +import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; -export class ConfigurationFileService implements IConfigurationFileService { +export class ConfigurationFileService extends Disposable implements IConfigurationFileService { + + private _fileServiceBasedConfigurationFileService: FileServiceBasedConfigurationFileService | null = null; + private _fileServiceBasedConfigurationFileServiceCallback: (fileServiceBasedConfigurationFileService: FileServiceBasedConfigurationFileService) => void; + private _whenFileServiceBasedConfigurationFileServiceAvailable: Promise = new Promise((c) => this._fileServiceBasedConfigurationFileServiceCallback = c); + private _watchResources: { resource: URI, disposable: { disposable: IDisposable | null } }[] = []; + readonly whenWatchingStarted: Promise = this._whenFileServiceBasedConfigurationFileServiceAvailable.then(() => undefined); + + private readonly _onFileChanges: Emitter = this._register(new Emitter()); + readonly onFileChanges: Event = this._onFileChanges.event; + + get isWatching(): boolean { + return this._fileServiceBasedConfigurationFileService ? this._fileServiceBasedConfigurationFileService.isWatching : false; + } + + watch(resource: URI): IDisposable { + if (this._fileServiceBasedConfigurationFileService) { + return this._fileServiceBasedConfigurationFileService.watch(resource); + } + const disposable: { disposable: IDisposable | null } = { disposable: null }; + this._watchResources.push({ resource, disposable }); + return toDisposable(() => { + if (disposable.disposable) { + disposable.disposable.dispose(); + } + }); + } + + whenProviderRegistered(scheme: string): Promise { + if (scheme === Schemas.file) { + return Promise.resolve(); + } + return this._whenFileServiceBasedConfigurationFileServiceAvailable + .then(fileServiceBasedConfigurationFileService => fileServiceBasedConfigurationFileService.whenProviderRegistered(scheme)); + } exists(resource: URI): Promise { - return pfs.exists(resource.fsPath); + return this._fileServiceBasedConfigurationFileService ? this._fileServiceBasedConfigurationFileService.exists(resource) : pfs.exists(resource.fsPath); } async resolveContent(resource: URI): Promise { - const contents = await pfs.readFile(resource.fsPath); - return contents.toString(); + if (this._fileServiceBasedConfigurationFileService) { + return this._fileServiceBasedConfigurationFileService.resolveContent(resource); + } else { + const contents = await pfs.readFile(resource.fsPath); + return contents.toString(); + } } + private _fileService: IFileService | null; + get fileService(): IFileService | null { + return this._fileService; + } + + set fileService(fileService: IFileService | null) { + if (fileService && !this._fileServiceBasedConfigurationFileService) { + this._fileServiceBasedConfigurationFileService = new FileServiceBasedConfigurationFileService(fileService); + this._fileService = fileService; + this._register(this._fileServiceBasedConfigurationFileService.onFileChanges(e => this._onFileChanges.fire(e))); + for (const { resource, disposable } of this._watchResources) { + disposable.disposable = this._fileServiceBasedConfigurationFileService.watch(resource); + } + this._fileServiceBasedConfigurationFileServiceCallback(this._fileServiceBasedConfigurationFileService); + } + } } diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 2932de2a5b..23c2e85568 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -55,7 +55,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic if (activeEditor instanceof DiffEditorInput) { activeEditor = activeEditor.modifiedInput; } - const fileResource = toResource(activeEditor, { filter: Schemas.file }); + const fileResource = toResource(activeEditor, { filterByScheme: Schemas.file }); if (!fileResource) { return undefined; } diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index b663d0bb66..f60cf09465 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -106,7 +106,7 @@ export class FileDialogService implements IFileDialogService { return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { if (uri) { return (this.fileService.resolve(uri)).then(stat => { - const toOpen: IURIToOpen = stat.isDirectory ? { fileUri: uri } : { folderUri: uri }; + const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); }); } @@ -210,7 +210,7 @@ export class FileDialogService implements IFileDialogService { showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (schema !== Schemas.file) { + if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow loading in the own file system } diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 5a6b96ff62..3a2fa7057b 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -110,10 +110,13 @@ export class RemoteFileDialog { let defaultUri = options.defaultUri; const filename = (defaultUri && isSave && (resources.dirname(defaultUri).path === '/')) ? resources.basename(defaultUri) : undefined; if (!defaultUri || filename) { - const env = await this.remoteAgentService.getEnvironment(); - if (env) { - defaultUri = env.userHome; - } else { + if (this.scheme !== Schemas.file) { + const env = await this.remoteAgentService.getEnvironment(); + if (env) { + defaultUri = env.userHome; + } + } + if (!defaultUri) { defaultUri = URI.from({ scheme: this.scheme, path: this.environmentService.userHome }); } if (filename) { @@ -192,7 +195,7 @@ export class RemoteFileDialog { if (validated) { isResolving = true; this.filePickBox.hide(); - resolve(resolveValue); + doResolve(this, resolveValue); } }); }); @@ -200,6 +203,13 @@ export class RemoteFileDialog { this.filePickBox.title = this.options.title; this.filePickBox.value = this.pathFromUri(this.currentFolder); this.filePickBox.items = []; + + function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) { + resolve(uri); + dialog.contextKey.set(false); + dialog.filePickBox.dispose(); + } + this.filePickBox.onDidAccept(_ => { if (isAcceptHandled || this.filePickBox.busy) { return; @@ -210,9 +220,9 @@ export class RemoteFileDialog { this.onDidAccept().then(resolveValue => { if (resolveValue) { this.filePickBox.hide(); - resolve(resolveValue); + doResolve(this, resolveValue); } else if (this.hidden) { - resolve(undefined); + doResolve(this, undefined); } else { isResolving = false; isAcceptHandled = false; @@ -221,6 +231,10 @@ export class RemoteFileDialog { }); this.filePickBox.onDidChangeActive(i => { isAcceptHandled = false; + // update input box to match the first selected item + if (i.length === 1) { + this.setAutoComplete(this.userValue, resources.basename(this.remoteUriFrom(this.userValue)), i[0], true); + } }); this.filePickBox.onDidChangeValue(async value => { @@ -229,13 +243,11 @@ export class RemoteFileDialog { if (value !== this.userValue) { this.filePickBox.validationMessage = undefined; this.shouldOverwriteFile = false; - const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value; - const valueUri = this.remoteUriFrom(trimmedPickBoxValue); - if (!resources.isEqual(this.currentFolder, valueUri, true)) { + const valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value)); + if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), valueUri, true)) { await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value)); } this.setActiveItems(value); - this.userValue = value; } else { this.filePickBox.activeItems = []; } @@ -244,10 +256,8 @@ export class RemoteFileDialog { this.filePickBox.onDidHide(() => { this.hidden = true; if (!isResolving) { - resolve(undefined); + doResolve(this, undefined); } - this.contextKey.set(false); - this.filePickBox.dispose(); }); this.filePickBox.show(); @@ -361,15 +371,8 @@ export class RemoteFileDialog { let hasMatch = false; for (let i = 0; i < this.filePickBox.items.length; i++) { const item = this.filePickBox.items[i]; - const itemBasename = (item.label === '..') ? item.label : resources.basename(item.uri); - if ((itemBasename.length >= inputBasename.length) && (itemBasename.substr(0, inputBasename.length).toLowerCase() === inputBasename.toLowerCase())) { + if (this.setAutoComplete(value, inputBasename, item)) { this.filePickBox.activeItems = [item]; - const insertValue = itemBasename.substr(inputBasename.length); - this.autoComplete = value + insertValue; - if (this.filePickBox.inputHasFocus()) { - document.execCommand('insertText', false, insertValue); - this.filePickBox.valueSelection = [value.length, this.filePickBox.value.length]; - } hasMatch = true; break; } @@ -377,6 +380,44 @@ export class RemoteFileDialog { if (!hasMatch) { this.filePickBox.activeItems = []; } + } else { + this.userValue = value; + this.autoComplete = ''; + } + } + + private setAutoComplete(startingValue: string, startingBasename: string, quickPickItem: FileQuickPickItem, force: boolean = false): boolean { + const itemBasename = (quickPickItem.label === '..') ? quickPickItem.label : resources.basename(quickPickItem.uri); + const itemPathLabel = (itemBasename === '..') ? this.pathAppend(this.currentFolder, itemBasename) : this.pathFromUri(quickPickItem.uri); + if (this.trimTrailingSlash(this.filePickBox.value) !== itemPathLabel) { + // Either foce the autocomplete, or the old value should be one smaller than the new value and match the new value. + if (!force && (itemBasename.length >= startingBasename.length) && (itemBasename.substr(0, startingBasename.length).toLowerCase() === startingBasename.toLowerCase())) { + this.userValue = startingValue; + const autoCompleteValue = itemBasename.substr(startingBasename.length); + this.autoComplete = startingValue + autoCompleteValue; + this.insertText(this.autoComplete, autoCompleteValue); + this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length]; + return true; + } else if (force) { + this.userValue = this.pathFromUri(this.currentFolder, true); + this.autoComplete = this.pathAppend(this.currentFolder, itemBasename); + this.filePickBox.valueSelection = [this.userValue.length, this.filePickBox.value.length]; + // use insert text to preserve undo buffer + this.insertText(this.autoComplete, itemBasename); + this.filePickBox.valueSelection = [this.filePickBox.value.length - itemBasename.length, this.filePickBox.value.length]; + return true; + } + } + this.userValue = startingValue; + this.autoComplete = ''; + return false; + } + + private insertText(wholeValue: string, insertText: string) { + if (this.filePickBox.inputHasFocus()) { + document.execCommand('insertText', false, insertText); + } else { + this.filePickBox.value = wholeValue; } } @@ -406,6 +447,39 @@ export class RemoteFileDialog { return result; } + private trimTrailingSlash(path: string): string { + return ((path.length > 1) && this.endsWithSlash(path)) ? path.substr(0, path.length - 1) : path; + } + + private yesNoPrompt(message: string): Promise { + interface YesNoItem extends IQuickPickItem { + value: boolean; + } + const prompt = this.quickInputService.createQuickPick(); + const no = nls.localize('remoteFileDialog.no', 'No'); + prompt.items = [{ label: no, value: false }, { label: nls.localize('remoteFileDialog.yes', 'Yes'), value: true }]; + prompt.title = message; + prompt.placeholder = no; + let isResolving = false; + return new Promise(resolve => { + prompt.onDidAccept(() => { + isResolving = true; + prompt.hide(); + resolve(prompt.selectedItems ? prompt.selectedItems[0].value : false); + }); + prompt.onDidHide(() => { + if (!isResolving) { + resolve(false); + } + this.filePickBox.show(); + this.hidden = false; + this.filePickBox.items = this.filePickBox.items; + prompt.dispose(); + }); + prompt.show(); + }); + } + private async validate(uri: URI): Promise { let stat: IFileStat | undefined; let statDirname: IFileStat | undefined; @@ -424,8 +498,9 @@ export class RemoteFileDialog { } else if (stat && !this.shouldOverwriteFile) { // Replacing a file. this.shouldOverwriteFile = true; - this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateExisting', '{0} already exists. Are you sure you want to overwrite it?', resources.basename(uri)); - return Promise.resolve(false); + // Show a yes/no prompt + const message = nls.localize('remoteFileDialog.validateExisting', '{0} already exists. Are you sure you want to overwrite it?', resources.basename(uri)); + return this.yesNoPrompt(message); } else if (!this.isValidBaseName(resources.basename(uri))) { // Filename not allowed this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateBadFilename', 'Please enter a valid file name.'); @@ -455,7 +530,11 @@ export class RemoteFileDialog { private updateItems(newFolder: URI, trailing?: string) { this.currentFolder = newFolder; - this.filePickBox.value = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); + this.userValue = this.pathFromUri(newFolder); + this.autoComplete = ''; + this.filePickBox.valueSelection = [0, this.filePickBox.value.length]; + const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); + this.insertText(newValue, newValue); this.filePickBox.busy = true; this.createItems(this.currentFolder).then(items => { this.filePickBox.items = items; @@ -480,6 +559,14 @@ export class RemoteFileDialog { return result; } + private pathAppend(uri: URI, additional: string): string { + if (additional === '..') { + return this.pathFromUri(uri) + this.labelService.getSeparator(uri.scheme, uri.authority) + additional; + } else { + return this.pathFromUri(resources.joinPath(uri, additional)); + } + } + private isValidBaseName(name: string): boolean { if (!name || name.length === 0 || /^\s+$/.test(name)) { return false; // require a name that is not just whitespace diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index a7e8483c02..9853da28a2 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,7 +5,7 @@ import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, ITextEditorOptions, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -419,7 +419,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Resource editor else { for (const editorInGroup of group.editors) { - const resource = toResource(editorInGroup, { supportSideBySide: true }); + const resource = toResource(editorInGroup, { supportSideBySide: SideBySideEditor.MASTER }); if (!resource) { continue; // need a resource to compare with } diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts similarity index 89% rename from src/vs/platform/extensionManagement/common/extensionEnablementService.ts rename to src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts index f9feeb721f..74c47865b0 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts @@ -6,13 +6,17 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, DidInstallExtensionEvent, InstallOperation, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled'; @@ -30,7 +34,10 @@ export class ExtensionEnablementService extends Disposable implements IExtension @IStorageService storageService: IStorageService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IWindowService private readonly windowService: IWindowService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, ) { super(); this.storageManger = this._register(new StorageManager(storageService)); @@ -47,38 +54,8 @@ export class ExtensionEnablementService extends Disposable implements IExtension return this.environmentService.disableExtensions === true; } - async getDisabledExtensions(): Promise { - - let result = this._getDisabledExtensions(StorageScope.GLOBAL); - - if (this.hasWorkspace) { - for (const e of this._getDisabledExtensions(StorageScope.WORKSPACE)) { - if (!result.some(r => areSameExtensions(r, e))) { - result.push(e); - } - } - const workspaceEnabledExtensions = this._getEnabledExtensions(StorageScope.WORKSPACE); - if (workspaceEnabledExtensions.length) { - result = result.filter(r => !workspaceEnabledExtensions.some(e => areSameExtensions(e, r))); - } - } - - if (this.environmentService.disableExtensions) { - const allInstalledExtensions = await this.extensionManagementService.getInstalled(); - for (const installedExtension of allInstalledExtensions) { - if (this._isExtensionDisabledInEnvironment(installedExtension)) { - if (!result.some(r => areSameExtensions(r, installedExtension.identifier))) { - result.push(installedExtension.identifier); - } - } - } - } - - return result; - } - getEnablementState(extension: IExtension): EnablementState { - if (this._isExtensionDisabledInEnvironment(extension)) { + if (this._isSystemDisabled(extension)) { return EnablementState.Disabled; } const identifier = extension.identifier; @@ -101,7 +78,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension if (extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) { return false; } - if (extension.type === ExtensionType.User && this.environmentService.disableExtensions) { + if (this._isSystemDisabled(extension)) { return false; } return true; @@ -153,7 +130,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled; } - private _isExtensionDisabledInEnvironment(extension: IExtension): boolean { + private _isSystemDisabled(extension: IExtension): boolean { if (this.allUserExtensionsDisabled) { return extension.type === ExtensionType.User; } @@ -161,6 +138,10 @@ export class ExtensionEnablementService extends Disposable implements IExtension if (Array.isArray(disabledExtensions)) { return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier)); } + if (this.windowService.getConfiguration().remoteAuthority) { + const server = isUIExtension(extension.manifest, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer; + return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; + } return false; } @@ -260,7 +241,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension return false; } - private _getEnabledExtensions(scope: StorageScope): IExtensionIdentifier[] { + protected _getEnabledExtensions(scope: StorageScope): IExtensionIdentifier[] { return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, scope); } @@ -268,7 +249,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions, scope); } - private _getDisabledExtensions(scope: StorageScope): IExtensionIdentifier[] { + protected _getDisabledExtensions(scope: StorageScope): IExtensionIdentifier[] { return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, scope); } @@ -386,3 +367,5 @@ class StorageManager extends Disposable { } } } + +registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts similarity index 77% rename from src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts rename to src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index ca1813fe5e..f15cedb706 100644 --- a/src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -4,15 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, ILocalExtension, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, ILocalExtension, DidInstallExtensionEvent, InstallOperation, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/node/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, InMemoryStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; import { isUndefinedOrNull } from 'vs/base/common/types'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { TestWindowService } from 'vs/workbench/test/workbenchTestServices'; function storageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -33,11 +37,22 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { super(storageService(instantiationService), instantiationService.get(IWorkspaceContextService), instantiationService.get(IEnvironmentService) || instantiationService.stub(IEnvironmentService, {} as IEnvironmentService), instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, - { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService)); + { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), + instantiationService.stub(IWindowService, new TestWindowService()), instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService)); } - public async reset(): Promise { - return this.getDisabledExtensions().then(extensions => extensions.forEach(d => this.setEnablement([aLocalExtension(d.id)], EnablementState.Enabled))); + public reset(): void { + let extensions = this._getDisabledExtensions(StorageScope.GLOBAL); + for (const e of this._getDisabledExtensions(StorageScope.WORKSPACE)) { + if (!extensions.some(r => areSameExtensions(r, e))) { + extensions.push(e); + } + } + const workspaceEnabledExtensions = this._getEnabledExtensions(StorageScope.WORKSPACE); + if (workspaceEnabledExtensions.length) { + extensions = extensions.filter(r => !workspaceEnabledExtensions.some(e => areSameExtensions(e, r))); + } + extensions.forEach(d => this.setEnablement([aLocalExtension(d.id)], EnablementState.Enabled)); } } @@ -59,22 +74,11 @@ suite('ExtensionEnablementService Test', () => { (testObject).dispose(); }); - test('test when no extensions are disabled', () => { - return testObject.getDisabledExtensions().then(extensions => assert.deepEqual([], extensions)); - }); - - test('test when no extensions are disabled for workspace when there is no workspace', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => { - instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY); - return testObject.getDisabledExtensions().then(extensions => assert.deepEqual([], extensions)); - }); - }); - - test('test disable an extension globally', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions)); + test('test disable an extension globally', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.Disabled); + assert.ok(!testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.Disabled); }); test('test disable an extension globally should return truthy promise', () => { @@ -109,10 +113,11 @@ suite('ExtensionEnablementService Test', () => { .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled)); }); - test('test disable an extension for workspace', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions)); + test('test disable an extension for workspace', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + assert.ok(!testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.WorkspaceDisabled); }); test('test disable an extension for workspace returns a truthy promise', () => { @@ -183,11 +188,12 @@ suite('ExtensionEnablementService Test', () => { .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled)); }); - test('test disable an extension for workspace and then globally', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions)); + test('test disable an extension for workspace and then globally', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + await testObject.setEnablement([extension], EnablementState.Disabled); + assert.ok(!testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.Disabled); }); test('test disable an extension for workspace and then globally return a truthy promise', () => { @@ -207,11 +213,12 @@ suite('ExtensionEnablementService Test', () => { }); }); - test('test disable an extension globally and then for workspace', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions)); + test('test disable an extension globally and then for workspace', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.Disabled); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + assert.ok(!testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.WorkspaceDisabled); }); test('test disable an extension globally and then for workspace return a truthy promise', () => { @@ -237,11 +244,12 @@ suite('ExtensionEnablementService Test', () => { .then(() => assert.fail('should throw an error'), error => assert.ok(error)); }); - test('test enable an extension globally', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled)) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([], extensions)); + test('test enable an extension globally', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.Disabled); + await testObject.setEnablement([extension], EnablementState.Enabled); + assert.ok(testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.Enabled); }); test('test enable an extension globally return truthy promise', () => { @@ -266,11 +274,12 @@ suite('ExtensionEnablementService Test', () => { .then(value => assert.ok(!value[0])); }); - test('test enable an extension for workspace', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceEnabled)) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([], extensions)); + test('test enable an extension for workspace', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + await testObject.setEnablement([extension], EnablementState.WorkspaceEnabled); + assert.ok(testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.WorkspaceEnabled); }); test('test enable an extension for workspace return truthy promise', () => { @@ -295,36 +304,39 @@ suite('ExtensionEnablementService Test', () => { .then(value => assert.ok(value)); }); - test('test enable an extension for workspace when disabled in workspace and gloablly', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceEnabled)) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([], extensions)); + test('test enable an extension for workspace when disabled in workspace and gloablly', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + await testObject.setEnablement([extension], EnablementState.Disabled); + await testObject.setEnablement([extension], EnablementState.WorkspaceEnabled); + assert.ok(testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.WorkspaceEnabled); }); - test('test enable an extension globally when disabled in workspace and gloablly', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled)) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([], extensions)); + test('test enable an extension globally when disabled in workspace and gloablly', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.WorkspaceEnabled); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + await testObject.setEnablement([extension], EnablementState.Disabled); + await testObject.setEnablement([extension], EnablementState.Enabled); + assert.ok(testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.Enabled); }); test('test installing an extension re-eanbles it when disabled globally', async () => { const local = aLocalExtension('pub.a'); await testObject.setEnablement([local], EnablementState.Disabled); didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - const extensions = await testObject.getDisabledExtensions(); - assert.deepEqual([], extensions); + assert.ok(testObject.isEnabled(local)); + assert.equal(testObject.getEnablementState(local), EnablementState.Enabled); }); test('test updating an extension does not re-eanbles it when disabled globally', async () => { const local = aLocalExtension('pub.a'); await testObject.setEnablement([local], EnablementState.Disabled); didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); - const extensions = await testObject.getDisabledExtensions(); - assert.deepEqual([{ id: 'pub.a' }], extensions); + assert.ok(!testObject.isEnabled(local)); + assert.equal(testObject.getEnablementState(local), EnablementState.Disabled); }); test('test installing an extension fires enablement change event when disabled globally', async () => { @@ -353,16 +365,16 @@ suite('ExtensionEnablementService Test', () => { const local = aLocalExtension('pub.a'); await testObject.setEnablement([local], EnablementState.WorkspaceDisabled); didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - const extensions = await testObject.getDisabledExtensions(); - assert.deepEqual([], extensions); + assert.ok(testObject.isEnabled(local)); + assert.equal(testObject.getEnablementState(local), EnablementState.Enabled); }); test('test updating an extension does not re-eanbles it when workspace disabled', async () => { const local = aLocalExtension('pub.a'); await testObject.setEnablement([local], EnablementState.WorkspaceDisabled); didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); - const extensions = await testObject.getDisabledExtensions(); - assert.deepEqual([{ id: 'pub.a' }], extensions); + assert.ok(!testObject.isEnabled(local)); + assert.equal(testObject.getEnablementState(local), EnablementState.WorkspaceDisabled); }); test('test installing an extension fires enablement change event when workspace disabled', async () => { @@ -395,12 +407,13 @@ suite('ExtensionEnablementService Test', () => { assert.ok(!target.called); }); - test('test remove an extension from disablement list when uninstalled', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)) - .then(() => didUninstallEvent.fire({ identifier: { id: 'pub.a' } })) - .then(() => testObject.getDisabledExtensions()) - .then(extensions => assert.deepEqual([], extensions)); + test('test remove an extension from disablement list when uninstalled', async () => { + const extension = aLocalExtension('pub.a'); + await testObject.setEnablement([extension], EnablementState.WorkspaceDisabled); + await testObject.setEnablement([extension], EnablementState.Disabled); + didUninstallEvent.fire({ identifier: { id: 'pub.a' } }); + assert.ok(testObject.isEnabled(extension)); + assert.equal(testObject.getEnablementState(extension), EnablementState.Enabled); }); test('test isEnabled return false extension is disabled globally', () => { @@ -442,22 +455,20 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(extension), true); }); - test('test canChangeEnablement return false for system extensions when extension is disabled in environment', () => { + test('test canChangeEnablement return false for system extension when extension is disabled in environment', () => { instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService); testObject = new TestExtensionEnablementService(instantiationService); const extension = aLocalExtension('pub.a', undefined, ExtensionType.System); - assert.equal(testObject.canChangeEnablement(extension), true); + assert.ok(!testObject.canChangeEnablement(extension)); }); - test('test getDisabledExtensions include extensions disabled in enviroment', () => { + test('test extension is disabled when disabled in enviroment', async () => { + const extension = aLocalExtension('pub.a'); instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService); - instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([aLocalExtension('pub.a'), aLocalExtension('pub.b')]) } as IExtensionManagementService); + instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService); testObject = new TestExtensionEnablementService(instantiationService); - return testObject.getDisabledExtensions() - .then(actual => { - assert.equal(actual.length, 1); - assert.equal(actual[0].id, 'pub.a'); - }); + assert.ok(!testObject.isEnabled(extension)); + assert.deepEqual(testObject.getEnablementState(extension), EnablementState.Disabled); }); }); diff --git a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts index 4daa0ac086..c3416a958d 100644 --- a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts +++ b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts @@ -16,8 +16,19 @@ export interface IExtensionDevOptions { export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions { // handle extension host lifecycle a bit special when we know we are developing an extension that runs inside let isExtensionDevHost = environmentService.isExtensionDevelopment; - const extDevLoc = environmentService.extensionDevelopmentLocationURI; - const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file; + + let debugOk = true; + let extDevLoc = environmentService.extensionDevelopmentLocationURI; + if (Array.isArray(extDevLoc)) { + for (let x of extDevLoc) { + if (x.scheme !== Schemas.file) { + debugOk = false; + } + } + } else if (extDevLoc && extDevLoc.scheme !== Schemas.file) { + debugOk = false; + } + let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index c3d9261569..dcbe8ca21a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -294,10 +294,30 @@ export class CachedExtensionScanner { // Always load developed extensions while extensions development let developedExtensions: Promise = Promise.resolve([]); - if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) { - developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions( - new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log - ); + if (environmentService.isExtensionDevelopment) { + + if (Array.isArray(environmentService.extensionDevelopmentLocationURI)) { + + const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => { + return ExtensionScanner.scanOneOrMultipleExtensions( + new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log + ); + }); + developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => { + let extDesc: IExtensionDescription[] = []; + for (let eds of extDescArrays) { + extDesc = extDesc.concat(eds); + } + return extDesc; + }); + + } else if (environmentService.extensionDevelopmentLocationURI) { + if (environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) { + developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions( + new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log + ); + } + } } return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 797f2bb27f..af0c8bfd8d 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -13,8 +13,9 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; -import { isWindows } from 'vs/base/common/platform'; +import * as platform from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; +import pkg from 'vs/platform/product/node/package'; import { URI } from 'vs/base/common/uri'; import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; import { findFreePort, randomPort } from 'vs/base/node/ports'; @@ -102,12 +103,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate())); this._toDispose.push(this._extensionHostDebugService.onClose(resource => { - if (this._isExtensionDevHost && isEqual(resource, this._environmentService.extensionDevelopmentLocationURI)) { + if (this._isExtensionDevHost && this.matchesExtDevLocations(resource)) { this._windowService.closeWindow(); } })); this._toDispose.push(this._extensionHostDebugService.onReload(resource => { - if (this._isExtensionDevHost && isEqual(resource, this._environmentService.extensionDevelopmentLocationURI)) { + if (this._isExtensionDevHost && this.matchesExtDevLocations(resource)) { this._windowService.reloadWindow(); } })); @@ -119,6 +120,18 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { })); } + // returns true if the given resource url matches one of the extension development paths passed to VS Code + private matchesExtDevLocations(resource: URI): boolean { + + const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; + if (Array.isArray(extDevLocs)) { + return extDevLocs.some(extDevLoc => isEqual(extDevLoc, resource)); + } else if (extDevLocs) { + return isEqual(extDevLocs, resource); + } + return false; + } + public dispose(): void { this.terminate(); } @@ -148,7 +161,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // and detach under Linux and Mac create another process group. // We detach because we have noticed that when the renderer exits, its child processes // (i.e. extension host) are taken down in a brutal fashion by the OS - detached: !!isWindows, + detached: !!platform.isWindows, execArgv: undefined as string[] | undefined, silent: true }; @@ -389,11 +402,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { const workspace = this._contextService.getWorkspace(); const r: IInitData = { commit: product.commit, + version: pkg.version, parentPid: process.pid, environment: { isExtensionDevelopmentDebug: this._isExtensionDevDebug, appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : undefined, + appName: product.nameLong, + appUriScheme: product.urlProtocol, + appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: URI.file(this._environmentService.globalStorageHome), diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 08e94b297e..f82a06d930 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -645,73 +645,84 @@ export class ExtensionService extends Disposable implements IExtensionService { this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); } - private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { - return this._extensionEnablementService.getDisabledExtensions() - .then(disabledExtensions => { - - const runtimeExtensions: IExtensionDescription[] = []; - const extensionsToDisable: IExtensionDescription[] = []; - const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }]; - - let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || []; - - const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id); - - if (enableProposedApiFor.length) { - let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]); - allProposed.forEach(id => { - if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) { - console.error(notFound(id)); - } - }); - // Make enabled proposed API be lowercase for case insensitive comparison - if (Array.isArray(enableProposedApiFor)) { - enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase()); - } else { - enableProposedApiFor = enableProposedApiFor.toLowerCase(); + private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { + if (this._environmentService.isExtensionDevelopment) { + const extDevLoc = this._environmentService.extensionDevelopmentLocationURI; + const extLocation = extension.extensionLocation; + if (Array.isArray(extDevLoc)) { + for (let p of extDevLoc) { + if (isEqualOrParent(extLocation, p)) { + return true; } } + } else if (extDevLoc) { + return isEqualOrParent(extLocation, extDevLoc); + } + } + return false; + } - const enableProposedApiForAll = !this._environmentService.isBuilt || - (!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') || - (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); + private async _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { - for (const extension of allExtensions) { - const isExtensionUnderDevelopment = ( - this._environmentService.isExtensionDevelopment - && this._environmentService.extensionDevelopmentLocationURI - && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI) - ); - // Do not disable extensions under development - if (!isExtensionUnderDevelopment) { - if (disabledExtensions.some(disabled => areSameExtensions(disabled, { id: extension.identifier.value }))) { - continue; - } - } + const runtimeExtensions: IExtensionDescription[] = []; + const extensionsToDisable: IExtensionDescription[] = []; + const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }]; - if (!extension.isBuiltin) { - // Check if the extension is changed to system extension - const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0]; - if (userMigratedSystemExtension) { - extensionsToDisable.push(extension); - continue; - } - } - runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor)); - } + let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || []; - this._telemetryService.publicLog('extensionsScanned', { - totalCount: runtimeExtensions.length, - disabledCount: disabledExtensions.length - }); + const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id); - if (extensionsToDisable.length) { - return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled) - .then(() => runtimeExtensions); - } else { - return runtimeExtensions; + if (enableProposedApiFor.length) { + let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]); + allProposed.forEach(id => { + if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) { + console.error(notFound(id)); } }); + // Make enabled proposed API be lowercase for case insensitive comparison + if (Array.isArray(enableProposedApiFor)) { + enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase()); + } else { + enableProposedApiFor = enableProposedApiFor.toLowerCase(); + } + } + + const enableProposedApiForAll = !this._environmentService.isBuilt || + (!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') || + (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); + + + for (const extension of allExtensions) { + + // Do not disable extensions under development + if (!this.isExtensionUnderDevelopment(extension)) { + if (!this._extensionEnablementService.isEnabled(toExtension(extension))) { + continue; + } + } + + if (!extension.isBuiltin) { + // Check if the extension is changed to system extension + const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0]; + if (userMigratedSystemExtension) { + extensionsToDisable.push(extension); + continue; + } + } + runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor)); + } + + this._telemetryService.publicLog('extensionsScanned', { + totalCount: runtimeExtensions.length, + disabledCount: allExtensions.length - runtimeExtensions.length + }); + + if (extensionsToDisable.length) { + return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled) + .then(() => runtimeExtensions); + } else { + return runtimeExtensions; + } } private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription { diff --git a/src/vs/workbench/services/files2/common/fileService2.ts b/src/vs/workbench/services/files2/common/fileService2.ts index 4d0d54465e..ac572eecb1 100644 --- a/src/vs/workbench/services/files2/common/fileService2.ts +++ b/src/vs/workbench/services/files2/common/fileService2.ts @@ -24,6 +24,8 @@ export class FileService2 extends Disposable implements IFileService { private joinOnLegacy: Promise; private joinOnImplResolve: (service: ILegacyFileService) => void; + get whenReady(): Promise { return this.joinOnLegacy.then(() => undefined); } + setLegacyService(legacy: ILegacyFileService): void { this._legacy = this._register(legacy); diff --git a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts b/src/vs/workbench/services/files2/test/node/diskFileService.test.ts index cf5370b81b..18e3fb2c8b 100644 --- a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files2/test/node/diskFileService.test.ts @@ -15,7 +15,7 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'fs'; -import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -934,6 +934,10 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - rename file', done => { + if (!isLinux) { + return done(); // not happy + } + const watchDir = URI.file(join(testDir, 'watch8')); mkdirSync(watchDir.fsPath); @@ -976,19 +980,23 @@ suite('Disk File Service', () => { } } + function printEvents(event: FileChangesEvent): string { + return event.changes.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); + } + const listenerDisposable = service.onFileChanges(event => { watcherDisposable.dispose(); listenerDisposable.dispose(); try { - assert.equal(event.changes.length, expected.length); + assert.equal(event.changes.length, expected.length, `Expected ${expected.length} events, but got ${event.changes.length}. Details (${printEvents(event)})`); if (expected.length === 1) { - assert.equal(event.changes[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(event.changes[0].type)}`); + assert.equal(event.changes[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(event.changes[0].type)}. Details (${printEvents(event)})`); assert.equal(event.changes[0].resource.fsPath, expected[0][1].fsPath); } else { for (const expect of expected) { - assert.equal(hasChange(event.changes, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}`); + assert.equal(hasChange(event.changes, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}. Details (${printEvents(event)})`); } } diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index b93d09c2a1..1af731d9ee 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -955,17 +955,17 @@ export class HistoryService extends Disposable implements IHistoryService { return undefined; } - getLastActiveFile(schemeFilter: string): URI | undefined { + getLastActiveFile(filterByScheme: string): URI | undefined { const history = this.getHistory(); for (const input of history) { let resource: URI | null; if (input instanceof EditorInput) { - resource = toResource(input, { filter: schemeFilter }); + resource = toResource(input, { filterByScheme }); } else { resource = (input as IResourceInput).resource; } - if (resource && resource.scheme === schemeFilter) { + if (resource && resource.scheme === filterByScheme) { return resource; } } diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 22b45d4e03..deb624a38e 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -12,7 +12,7 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn export interface IGetEnvironmentDataArguments { language: string; remoteAuthority: string; - extensionDevelopmentPath: UriComponents | undefined; + extensionDevelopmentPath: UriComponents | UriComponents[] | undefined; } export interface IRemoteAgentEnvironmentDTO { @@ -34,7 +34,7 @@ export class RemoteExtensionEnvironmentChannelClient { constructor(private channel: IChannel) { } - getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI): Promise { + getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI | URI[]): Promise { const args: IGetEnvironmentDataArguments = { language: platform.language, remoteAuthority, diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 3b8b930b5c..ba9d62560f 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -251,9 +251,20 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); + const extDevLoc = this.environmentService.extensionDevelopmentLocationURI; + let uri: URI | undefined; + if (Array.isArray(extDevLoc)) { + // if there are more than one ext dev paths, use first + if (extDevLoc.length > 0) { + uri = extDevLoc[0]; + } + } else { + uri = extDevLoc; + } + return Promise.all([ this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - return this.colorThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => { + return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } else { @@ -262,7 +273,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); }), this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - return this.iconThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => { + return this.iconThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } else { diff --git a/src/vs/workbench/test/common/editor/editor.test.ts b/src/vs/workbench/test/common/editor/editor.test.ts index 13f6438c92..2b154890d0 100644 --- a/src/vs/workbench/test/common/editor/editor.test.ts +++ b/src/vs/workbench/test/common/editor/editor.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorInput, toResource } from 'vs/workbench/common/editor'; +import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; @@ -60,27 +60,26 @@ suite('Workbench editor', () => { const untitled = service.createOrGet(); assert.equal(toResource(untitled)!.toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { supportSideBySide: true })!.toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { filter: Schemas.untitled })!.toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { filter: [Schemas.file, Schemas.untitled] })!.toString(), untitled.getResource().toString()); - assert.ok(!toResource(untitled, { filter: Schemas.file })); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), untitled.getResource().toString()); + assert.equal(toResource(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.getResource().toString()); + assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.getResource().toString()); + assert.ok(!toResource(untitled, { filterByScheme: Schemas.file })); const file = new FileEditorInput(URI.file('/some/path.txt')); assert.equal(toResource(file)!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: true })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { filter: Schemas.file })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { filter: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); - assert.ok(!toResource(file, { filter: Schemas.untitled })); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { filterByScheme: Schemas.file })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); + assert.ok(!toResource(file, { filterByScheme: Schemas.untitled })); const diffEditorInput = new DiffEditorInput('name', 'description', untitled, file); assert.ok(!toResource(diffEditorInput)); - assert.ok(!toResource(diffEditorInput, { filter: Schemas.file })); - assert.ok(!toResource(diffEditorInput, { supportSideBySide: false })); + assert.ok(!toResource(diffEditorInput, { filterByScheme: Schemas.file })); - assert.equal(toResource(file, { supportSideBySide: true })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: true, filter: Schemas.file })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: true, filter: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 36efd36757..ed59a63481 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -40,8 +40,7 @@ suite('ExtHostConfiguration', function () { user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), folders: Object.create(null), - configurationScopes: {}, - isComplete: true + configurationScopes: {} }; } @@ -279,8 +278,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']), workspace: new ConfigurationModel({}, []), folders: Object.create(null), - configurationScopes: {}, - isComplete: true + configurationScopes: {} } ); @@ -328,8 +326,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']), workspace, folders, - configurationScopes: {}, - isComplete: true + configurationScopes: {} } ); @@ -405,8 +402,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']), workspace, folders, - configurationScopes: {}, - isComplete: true + configurationScopes: {} } ); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 0bd4fa521a..c1d013188c 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -1046,9 +1046,9 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getLinks(model, CancellationToken.None); - assert.equal(value.length, 1); - let [first] = value; + let { links } = await getLinks(model, CancellationToken.None); + assert.equal(links.length, 1); + let [first] = links; assert.equal(first.url, 'foo:bar#3'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 }); }); @@ -1068,9 +1068,9 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getLinks(model, CancellationToken.None); - assert.equal(value.length, 1); - let [first] = value; + let { links } = await getLinks(model, CancellationToken.None); + assert.equal(links.length, 1); + let [first] = links; assert.equal(first.url, 'foo:bar#3'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 }); }); diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 16aa1e5aa9..f63c5d5748 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -62,8 +62,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { AccessibilityService } from 'vs/platform/accessibility/node/accessibilityService'; -import { IExtensionEnablementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; @@ -123,6 +122,7 @@ import 'vs/workbench/services/textfile/node/textResourcePropertiesService'; import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; +import 'vs/workbench/services/extensionManagement/node/extensionEnablementService'; import 'vs/workbench/services/extensions/electron-browser/extensionService'; import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; import 'vs/workbench/services/extensions/node/multiExtensionManagement'; @@ -144,7 +144,6 @@ registerSingleton(IContextKeyService, ContextKeyService); registerSingleton(IModelService, ModelServiceImpl, true); registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IAccessibilityService, AccessibilityService, true); -registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); registerSingleton(IContextViewService, ContextViewService, true); registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IRequestService, RequestService, true); diff --git a/src/vs/workbench/workbench.nodeless.main.ts b/src/vs/workbench/workbench.nodeless.main.ts index 1dbd859f2a..d14d57ff5e 100644 --- a/src/vs/workbench/workbench.nodeless.main.ts +++ b/src/vs/workbench/workbench.nodeless.main.ts @@ -62,8 +62,6 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; -import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; @@ -103,7 +101,6 @@ import 'vs/platform/dialogs/browser/dialogService'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; // import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; -import 'vs/workbench/services/hash/common/hashService'; // import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; // import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; @@ -132,6 +129,7 @@ import 'vs/workbench/services/untitled/common/untitledEditorService'; import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; +// import 'vs/workbench/services/extensionManagement/node/extensionEnablementService'; // import 'vs/workbench/services/extensions/electron-browser/extensionService'; // import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; // import 'vs/workbench/services/extensionManagement/node/multiExtensionManagement'; @@ -155,7 +153,6 @@ registerSingleton(IContextKeyService, ContextKeyService); registerSingleton(IModelService, ModelServiceImpl, true); registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); -registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); registerSingleton(IContextViewService, ContextViewService, true); // registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); // registerSingleton(IRequestService, RequestService, true); diff --git a/test/tree/package.json b/test/tree/package.json index 6826810e73..1a3b32d627 100644 --- a/test/tree/package.json +++ b/test/tree/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "main": "index.js", "license": "MIT", - "dependencies": { + "devDependencies": { "koa": "^2.5.1", "koa-mount": "^3.0.0", "koa-route": "^3.2.0", "koa-static": "^5.0.0", "mz": "^2.7.0" } -} +} \ No newline at end of file