diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 74fddcc55a..62ee67ad1c 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -1,6 +1,3 @@ -pool: - vmImage: 'Ubuntu-16.04' - trigger: branches: include: ['master', 'release/*'] diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 797c4b5fce..39a8f23b26 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -13,18 +13,28 @@ steps: inputs: versionSpec: "10.15.1" +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - script: | set -e cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF git config user.email "vscode@microsoft.com" git config user.name "VSCode" - git checkout origin/ben/electron-test + git checkout origin/electron-6.0.x git merge origin/master # Push master branch into exploration branch - git push origin HEAD:ben/electron-test + git push origin HEAD:electron-6.0.x displayName: Sync & Merge Exploration diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index a2fdc9e4ec..f38f431182 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -143,8 +143,8 @@ trigger: none pr: none schedules: -- cron: "10 5 * * Mon-Fri" - displayName: Mon-Fri at 7:10 +- cron: "0 5 * * Mon-Fri" + displayName: Mon-Fri at 7:00 branches: include: - master diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 55950a9021..c24857720e 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -129,6 +129,13 @@ "type": "string", "description": "The service you want to work on." }, + "runServices": { + "type": "array", + "description": "An array of services that should be started and stopped.", + "items": { + "type": "string" + } + }, "workspaceFolder": { "type": "string", "description": "The path of the workspace folder inside the container." @@ -178,4 +185,4 @@ "$ref": "#/definitions/devContainerCommon" } ] -} \ No newline at end of file +} diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index 23a4001a47..71dc4a18c4 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -11,14 +11,9 @@ VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" ELECTRON="$VSCODE_PATH/$NAME.exe" if grep -qi Microsoft /proc/version; then # in a wsl shell - if [ "$WSL_DISTRO_NAME" ]; then - # $WSL_DISTRO_NAME is available since WSL builds 18362, also for WSL2 - WSL_BUILD=18362 - else - WSL_BUILD=$(uname -r | sed -E 's/^.+-([0-9]+)-Microsoft/\1/') - if [ -z "$WSL_BUILD" ]; then - WSL_BUILD=0 - fi + WSL_BUILD=$(uname -r | sed -E 's/^[0-9.]+-([0-9]+)-Microsoft|([0-9]+).([0-9]+).([0-9]+)-microsoft-standard|.*/\1\2\3\4/') + if [ -z "$WSL_BUILD" ]; then + WSL_BUILD=0 fi if [ $WSL_BUILD -ge 17063 ]; then @@ -30,7 +25,18 @@ if grep -qi Microsoft /proc/version; then # use the Remote WSL extension if installed WSL_EXT_ID="ms-vscode-remote.remote-wsl" - WSL_EXT_WLOC=$(ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID) + + if [ $WSL_BUILD -ge 41955 ]; then + # WSL2 in workaround for https://github.com/microsoft/WSL/issues/4337 + CWD="$(pwd)" + cd "$VSCODE_PATH" + cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >remote-wsl-loc.txt" + WSL_EXT_WLOC="$(cat ./remote-wsl-loc.txt)" + rm remote-wsl-loc.txt + cd "$CWD" + else + WSL_EXT_WLOC=$(ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID) + fi if [ -n "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode.sh diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index cd96a8feb2..1e07043d5a 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -8,14 +8,6 @@ import { CharCode } from 'vs/base/common/charCode'; import { Iterator, IteratorResult, FIN } from './iterator'; -export function fromArray(array: readonly T[]): Set { - const result = new Set(); - for (const element of array) { - result.add(element); - } - return result; -} - export function values(set: Set): V[]; export function values(map: Map): V[]; export function values(forEachable: { forEach(callback: (value: V, ...more: any[]) => any): void }): V[] { diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index eede6caa6e..b26a5dded3 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -405,42 +405,53 @@ export class Client extends IPCClient { /** * Will ensure no messages are lost if there are no event listeners. */ -export function createBufferedEvent(source: Event): Event { - let emitter: Emitter; - let hasListeners = false; - let isDeliveringMessages = false; - let bufferedMessages: T[] = []; +export class BufferedEmitter { + private _emitter: Emitter; + public readonly event: Event; - const deliverMessages = () => { - if (isDeliveringMessages) { + private _hasListeners = false; + private _isDeliveringMessages = false; + private _bufferedMessages: T[] = []; + + constructor() { + this._emitter = new Emitter({ + onFirstListenerAdd: () => { + this._hasListeners = true; + // it is important to deliver these messages after this call, but before + // other messages have a chance to be received (to guarantee in order delivery) + // that's why we're using here nextTick and not other types of timeouts + process.nextTick(() => this._deliverMessages); + }, + onLastListenerRemove: () => { + this._hasListeners = false; + } + }); + + this.event = this._emitter.event; + } + + private _deliverMessages(): void { + if (this._isDeliveringMessages) { return; } - isDeliveringMessages = true; - while (hasListeners && bufferedMessages.length > 0) { - emitter.fire(bufferedMessages.shift()!); + this._isDeliveringMessages = true; + while (this._hasListeners && this._bufferedMessages.length > 0) { + this._emitter.fire(this._bufferedMessages.shift()!); } - isDeliveringMessages = false; - }; + this._isDeliveringMessages = false; + } - source((e: T) => { - bufferedMessages.push(e); - deliverMessages(); - }); - - emitter = new Emitter({ - onFirstListenerAdd: () => { - hasListeners = true; - // it is important to deliver these messages after this call, but before - // other messages have a chance to be received (to guarantee in order delivery) - // that's why we're using here nextTick and not other types of timeouts - process.nextTick(deliverMessages); - }, - onLastListenerRemove: () => { - hasListeners = false; + public fire(event: T): void { + if (this._hasListeners) { + this._emitter.fire(event); + } else { + this._bufferedMessages.push(event); } - }); + } - return emitter.event; + public flushBuffer(): void { + this._bufferedMessages = []; + } } class QueueElement { @@ -530,20 +541,20 @@ export class PersistentProtocol implements IMessagePassingProtocol { private _socketReader: ProtocolReader; private _socketDisposables: IDisposable[]; - private _onControlMessage = new Emitter(); - readonly onControlMessage: Event = createBufferedEvent(this._onControlMessage.event); + private readonly _onControlMessage = new BufferedEmitter(); + readonly onControlMessage: Event = this._onControlMessage.event; - private _onMessage = new Emitter(); - readonly onMessage: Event = createBufferedEvent(this._onMessage.event); + private readonly _onMessage = new BufferedEmitter(); + readonly onMessage: Event = this._onMessage.event; - private _onClose = new Emitter(); - readonly onClose: Event = createBufferedEvent(this._onClose.event); + private readonly _onClose = new BufferedEmitter(); + readonly onClose: Event = this._onClose.event; - private _onSocketClose = new Emitter(); - readonly onSocketClose: Event = createBufferedEvent(this._onSocketClose.event); + private readonly _onSocketClose = new BufferedEmitter(); + readonly onSocketClose: Event = this._onSocketClose.event; - private _onSocketTimeout = new Emitter(); - readonly onSocketTimeout: Event = createBufferedEvent(this._onSocketTimeout.event); + private readonly _onSocketTimeout = new BufferedEmitter(); + readonly onSocketTimeout: Event = this._onSocketTimeout.event; public get unacknowledgedCount(): number { return this._outgoingMsgId - this._outgoingAckId; @@ -656,6 +667,10 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._isReconnecting = true; this._socketDisposables = dispose(this._socketDisposables); + this._onControlMessage.flushBuffer(); + this._onSocketClose.flushBuffer(); + this._onSocketTimeout.flushBuffer(); + this._socket.dispose(); this._socket = socket; this._socketWriter = new ProtocolWriter(this._socket); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 60530cfd27..2df76a4aef 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -10,7 +10,6 @@ import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { compareAnything } from 'vs/base/common/comparers'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import * as DOM from 'vs/base/browser/dom'; @@ -576,37 +575,3 @@ export class QuickOpenModel implements return entry.run(mode, context); } } - -/** - * A good default sort implementation for quick open entries respecting highlight information - * as well as associated resources. - */ -export function compareEntries(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string): number { - - // Give matches with label highlights higher priority over - // those with only description highlights - const labelHighlightsA = elementA.getHighlights()[0] || []; - const labelHighlightsB = elementB.getHighlights()[0] || []; - if (labelHighlightsA.length && !labelHighlightsB.length) { - return -1; - } - - if (!labelHighlightsA.length && labelHighlightsB.length) { - return 1; - } - - // Fallback to the full path if labels are identical and we have associated resources - let nameA = elementA.getLabel()!; - let nameB = elementB.getLabel()!; - if (nameA === nameB) { - const resourceA = elementA.getResource(); - const resourceB = elementB.getResource(); - - if (resourceA && resourceB) { - nameA = resourceA.fsPath; - nameB = resourceB.fsPath; - } - } - - return compareAnything(nameA, nameB, lookFor); -} diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 9bf87281e2..9c7defe5af 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -4,29 +4,32 @@ - - - + + + - + - - + + + + - + - - + diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js index 815fb0ea05..9df7efece4 100644 --- a/src/vs/code/browser/workbench/workbench.js +++ b/src/vs/code/browser/workbench/workbench.js @@ -3,11 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +//@ts-check 'use strict'; (function () { - require.config({ + /** @type any */ + const amdLoader = require; + + amdLoader.config({ baseUrl: `${window.location.origin}/static/out`, paths: { 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, @@ -20,7 +24,7 @@ } }); - require(['vs/workbench/workbench.web.api'], function (api) { + amdLoader(['vs/workbench/workbench.web.api'], function (api) { const options = JSON.parse(document.getElementById('vscode-workbench-web-configuration').getAttribute('data-settings')); api.create(document.body, options); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 8c9da62f99..27782ac0e6 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -14,7 +14,10 @@ const bootstrapWindow = require('../../../../bootstrap-window'); // Setup shell environment process['lazyEnv'] = getLazyEnv(); -// Load workbench main +// Load workbench main JS, CSS and NLS all in parallel. This is an +// optimization to prevent a waterfall of loading to happen, because +// we know for a fact that workbench.desktop.main will depend on +// the related CSS and NLS counterparts. bootstrapWindow.load([ 'vs/workbench/workbench.desktop.main', 'vs/nls!vs/workbench/workbench.desktop.main', diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 131acb5bc1..3c20fb0231 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -40,8 +40,10 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { INotificationService } from 'vs/platform/notification/common/notification'; import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -// {{SQL CARBON EDIT}} -import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper'; +import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper'; // {{SQL CARBON EDIT}} +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; interface IEditorDiffDecorations { decorations: IModelDeltaDecoration[]; @@ -49,7 +51,7 @@ interface IEditorDiffDecorations { } interface IEditorDiffDecorationsWithZones extends IEditorDiffDecorations { - zones: editorBrowser.IViewZone[]; + zones: IMyViewZone[]; } interface IEditorsDiffDecorationsWithZones { @@ -58,8 +60,8 @@ interface IEditorsDiffDecorationsWithZones { } interface IEditorsZones { - original: editorBrowser.IViewZone[]; - modified: editorBrowser.IViewZone[]; + original: IMyViewZone[]; + modified: IMyViewZone[]; } interface IDiffEditorWidgetStyle { @@ -73,11 +75,16 @@ interface IDiffEditorWidgetStyle { class VisualEditorState { private _zones: string[]; + private inlineDiffMargins: InlineDiffMargin[]; private _zonesMap: { [zoneId: string]: boolean; }; private _decorations: string[]; - constructor() { + constructor( + private _contextMenuService: IContextMenuService, + private _clipboardService: IClipboardService + ) { this._zones = []; + this.inlineDiffMargins = []; this._zonesMap = {}; this._decorations = []; } @@ -111,13 +118,22 @@ class VisualEditorState { for (let i = 0, length = this._zones.length; i < length; i++) { viewChangeAccessor.removeZone(this._zones[i]); } + for (let i = 0, length = this.inlineDiffMargins.length; i < length; i++) { + this.inlineDiffMargins[i].dispose(); + } this._zones = []; this._zonesMap = {}; + this.inlineDiffMargins = []; for (let i = 0, length = newDecorations.zones.length; i < length; i++) { - newDecorations.zones[i].suppressMouseDown = true; - let zoneId = viewChangeAccessor.addZone(newDecorations.zones[i]); + const viewZone = newDecorations.zones[i]; + viewZone.suppressMouseDown = false; + let zoneId = viewChangeAccessor.addZone(viewZone); this._zones.push(zoneId); this._zonesMap[String(zoneId)] = true; + + if (newDecorations.zones[i].diff && viewZone.marginDomNode) { + this.inlineDiffMargins.push(new InlineDiffMargin(viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); + } } }); @@ -207,7 +223,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @IThemeService themeService: IThemeService, - @INotificationService notificationService: INotificationService + @INotificationService notificationService: INotificationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IClipboardService clipboardService: IClipboardService ) { super(); @@ -289,8 +307,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._currentlyChangingViewZones = false; this._diffComputationToken = 0; - this._originalEditorState = new VisualEditorState(); - this._modifiedEditorState = new VisualEditorState(); + this._originalEditorState = new VisualEditorState(contextMenuService, clipboardService); + this._modifiedEditorState = new VisualEditorState(contextMenuService, clipboardService); this._isVisible = true; this._isHandlingScrollEvent = false; @@ -1278,6 +1296,7 @@ interface IMyViewZone { minWidthInPx?: number; domNode: HTMLElement | null; marginDomNode?: HTMLElement | null; + diff?: IDiffLinesChange; } class ForeignViewZonesIterator { @@ -1489,12 +1508,12 @@ abstract class ViewZonesComputer { }; } - private static _ensureDomNodes(zones: IMyViewZone[]): editorBrowser.IViewZone[] { + private static _ensureDomNodes(zones: IMyViewZone[]): IMyViewZone[] { return zones.map((z) => { if (!z.domNode) { z.domNode = createFakeLinesDiv(); } - return z; + return z; }); } @@ -1997,8 +2016,10 @@ class InlineViewZonesComputer extends ViewZonesComputer { let lineHeight = this.modifiedEditorConfiguration.lineHeight; const typicalHalfwidthCharacterWidth = this.modifiedEditorConfiguration.fontInfo.typicalHalfwidthCharacterWidth; let maxCharsPerLine = 0; + const originalContent: string[] = []; for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) { maxCharsPerLine = Math.max(maxCharsPerLine, this._renderOriginalLine(lineNumber - lineChange.originalStartLineNumber, this.originalModel, this.modifiedEditorConfiguration, this.modifiedEditorTabSize, lineNumber, decorations, sb)); + originalContent.push(this.originalModel.getLineContent(lineNumber)); if (this.renderIndicators) { let index = lineNumber - lineChange.originalStartLineNumber; @@ -2025,7 +2046,14 @@ class InlineViewZonesComputer extends ViewZonesComputer { heightInLines: lineChangeOriginalLength, minWidthInPx: (maxCharsPerLine * typicalHalfwidthCharacterWidth), domNode: domNode, - marginDomNode: marginDomNode + marginDomNode: marginDomNode, + diff: { + originalStartLineNumber: lineChange.originalStartLineNumber, + originalEndLineNumber: lineChange.originalEndLineNumber, + modifiedStartLineNumber: lineChange.modifiedStartLineNumber, + modifiedEndLineNumber: lineChange.modifiedEndLineNumber, + originalContent: originalContent + } }; } diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 50c9d6696f..f7ffd038bb 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -16,6 +16,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @@ -74,9 +76,11 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @IThemeService themeService: IThemeService, - @INotificationService notificationService: INotificationService + @INotificationService notificationService: INotificationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IClipboardService clipboardService: IClipboardService ) { - super(domElement, parentEditor.getRawConfiguration(), editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService); + super(domElement, parentEditor.getRawConfiguration(), editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, clipboardService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/browser/widget/inlineDiffMargin.ts b/src/vs/editor/browser/widget/inlineDiffMargin.ts new file mode 100644 index 0000000000..b26e5a2958 --- /dev/null +++ b/src/vs/editor/browser/widget/inlineDiffMargin.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { Action } from 'vs/base/common/actions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { Range } from 'vs/editor/common/core/range'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; + +export interface IDiffLinesChange { + readonly originalStartLineNumber: number; + readonly originalEndLineNumber: number; + readonly modifiedStartLineNumber: number; + readonly modifiedEndLineNumber: number; + readonly originalContent: string[]; +} + +export class InlineDiffMargin extends Disposable { + private readonly _lightBulb: HTMLElement; + + constructor( + marginDomNode: HTMLElement, + public editor: CodeEditorWidget, + public diff: IDiffLinesChange, + private _contextMenuService: IContextMenuService, + private _clipboardService: IClipboardService + ) { + super(); + + // make sure the diff margin shows above overlay. + marginDomNode.style.zIndex = '10'; + + this._lightBulb = document.createElement('div'); + this._lightBulb.className = 'lightbulb-glyph'; + this._lightBulb.style.position = 'absolute'; + const lineHeight = editor.getConfiguration().lineHeight; + const lineFeed = editor.getModel()!.getEOL(); + this._lightBulb.style.right = '0px'; + this._lightBulb.style.visibility = 'hidden'; + this._lightBulb.style.height = `${lineHeight}px`; + marginDomNode.appendChild(this._lightBulb); + + const actions = [ + new Action( + 'diff.clipboard.copyDeletedContent', + nls.localize('diff.clipboard.copyDeletedContent.label', "Copy deleted lines content to clipboard"), + undefined, + true, + async () => { + await this._clipboardService.writeText(diff.originalContent.join(lineFeed) + lineFeed); + } + ) + ]; + + let currentLineNumberOffset = 0; + + const copyLineAction = new Action( + 'diff.clipboard.copyDeletedLineContent', + nls.localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line {0} content to clipboard", diff.originalStartLineNumber), + undefined, + true, + async () => { + await this._clipboardService.writeText(diff.originalContent[currentLineNumberOffset]); + } + ); + + actions.push(copyLineAction); + + const readOnly = editor.getConfiguration().readOnly; + if (!readOnly) { + actions.push(new Action('diff.inline.revertChange', nls.localize('diff.inline.revertChange.label', "Revert this change"), undefined, true, async () => { + if (diff.modifiedEndLineNumber === 0) { + // deletion only + const column = editor.getModel()!.getLineMaxColumn(diff.modifiedStartLineNumber); + editor.executeEdits('diffEditor', [ + { + range: new Range(diff.modifiedStartLineNumber, column, diff.modifiedStartLineNumber, column), + text: lineFeed + diff.originalContent.join(lineFeed) + } + ]); + } else { + const column = editor.getModel()!.getLineMaxColumn(diff.modifiedEndLineNumber); + editor.executeEdits('diffEditor', [ + { + range: new Range(diff.modifiedStartLineNumber, 1, diff.modifiedEndLineNumber, column), + text: diff.originalContent.join(lineFeed) + } + ]); + } + + })); + } + + this._register(dom.addStandardDisposableListener(marginDomNode, 'mouseenter', e => { + this._lightBulb.style.visibility = 'visible'; + currentLineNumberOffset = this._updateLightBulbPosition(marginDomNode, e.y, lineHeight); + })); + + this._register(dom.addStandardDisposableListener(marginDomNode, 'mouseleave', e => { + this._lightBulb.style.visibility = 'hidden'; + })); + + this._register(dom.addStandardDisposableListener(marginDomNode, 'mousemove', e => { + currentLineNumberOffset = this._updateLightBulbPosition(marginDomNode, e.y, lineHeight); + })); + + this._register(dom.addStandardDisposableListener(this._lightBulb, 'mousedown', e => { + const { top, height } = dom.getDomNodePagePosition(this._lightBulb); + let pad = Math.floor(lineHeight / 3) + lineHeight; + this._contextMenuService.showContextMenu({ + getAnchor: () => { + return { + x: e.posx, + y: top + height + pad + }; + }, + getActions: () => { + copyLineAction.label = nls.localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line {0} content to clipboard", diff.originalStartLineNumber + currentLineNumberOffset); + return actions; + }, + autoSelectFirstItem: true + }); + })); + } + + private _updateLightBulbPosition(marginDomNode: HTMLElement, y: number, lineHeight: number): number { + const { top } = dom.getDomNodePagePosition(marginDomNode); + const offset = y - top; + const lineNumberOffset = Math.floor(offset / lineHeight); + const newTop = lineNumberOffset * lineHeight; + this._lightBulb.style.top = `${newTop}px`; + return lineNumberOffset; + } +} diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index bb240ebf7c..dc5725a80f 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2191,7 +2191,7 @@ export class InternalEditorOptionsFactory { selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, revealHorizontalRightPadding: opts.viewInfo.revealHorizontalRightPadding, - roundedSelection: (accessibilityIsOn ? false : opts.viewInfo.roundedSelection), // DISABLED WHEN SCREEN READER IS ATTACHED + roundedSelection: opts.viewInfo.roundedSelection, overviewRulerLanes: opts.viewInfo.overviewRulerLanes, overviewRulerBorder: opts.viewInfo.overviewRulerBorder, cursorBlinking: opts.viewInfo.cursorBlinking, @@ -2204,15 +2204,15 @@ export class InternalEditorOptionsFactory { scrollBeyondLastColumn: opts.viewInfo.scrollBeyondLastColumn, smoothScrolling: opts.viewInfo.smoothScrolling, stopRenderingLineAfter: opts.viewInfo.stopRenderingLineAfter, - renderWhitespace: (accessibilityIsOn ? 'none' : opts.viewInfo.renderWhitespace), // DISABLED WHEN SCREEN READER IS ATTACHED - renderControlCharacters: (accessibilityIsOn ? false : opts.viewInfo.renderControlCharacters), // DISABLED WHEN SCREEN READER IS ATTACHED + renderWhitespace: opts.viewInfo.renderWhitespace, + renderControlCharacters: opts.viewInfo.renderControlCharacters, fontLigatures: opts.viewInfo.fontLigatures, - renderIndentGuides: (accessibilityIsOn ? false : opts.viewInfo.renderIndentGuides), // DISABLED WHEN SCREEN READER IS ATTACHED + renderIndentGuides: opts.viewInfo.renderIndentGuides, highlightActiveIndentGuide: opts.viewInfo.highlightActiveIndentGuide, renderLineHighlight: opts.viewInfo.renderLineHighlight, scrollbar: opts.viewInfo.scrollbar, minimap: { - enabled: (accessibilityIsOn ? false : opts.viewInfo.minimap.enabled), // DISABLED WHEN SCREEN READER IS ATTACHED + enabled: opts.viewInfo.minimap.enabled, side: opts.viewInfo.minimap.side, renderCharacters: opts.viewInfo.minimap.renderCharacters, showSlider: opts.viewInfo.minimap.showSlider, @@ -2224,7 +2224,7 @@ export class InternalEditorOptionsFactory { contribInfo: { selectionClipboard: opts.contribInfo.selectionClipboard, hover: opts.contribInfo.hover, - links: (accessibilityIsOn ? false : opts.contribInfo.links), // DISABLED WHEN SCREEN READER IS ATTACHED + links: opts.contribInfo.links, contextmenu: opts.contribInfo.contextmenu, quickSuggestions: opts.contribInfo.quickSuggestions, quickSuggestionsDelay: opts.contribInfo.quickSuggestionsDelay, @@ -2241,13 +2241,13 @@ export class InternalEditorOptionsFactory { tabCompletion: opts.contribInfo.tabCompletion, suggest: opts.contribInfo.suggest, gotoLocation: opts.contribInfo.gotoLocation, - selectionHighlight: (accessibilityIsOn ? false : opts.contribInfo.selectionHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED - occurrencesHighlight: (accessibilityIsOn ? false : opts.contribInfo.occurrencesHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED - codeLens: (accessibilityIsOn ? false : opts.contribInfo.codeLens), // DISABLED WHEN SCREEN READER IS ATTACHED + selectionHighlight: opts.contribInfo.selectionHighlight, + occurrencesHighlight: opts.contribInfo.occurrencesHighlight, + codeLens: opts.contribInfo.codeLens, folding: (accessibilityIsOn ? false : opts.contribInfo.folding), // DISABLED WHEN SCREEN READER IS ATTACHED foldingStrategy: opts.contribInfo.foldingStrategy, showFoldingControls: opts.contribInfo.showFoldingControls, - matchBrackets: (accessibilityIsOn ? false : opts.contribInfo.matchBrackets), // DISABLED WHEN SCREEN READER IS ATTACHED + matchBrackets: opts.contribInfo.matchBrackets, find: opts.contribInfo.find, colorDecorators: opts.contribInfo.colorDecorators, lightbulbEnabled: opts.contribInfo.lightbulbEnabled, diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 7d1614ffb9..9ab3fd7033 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -367,7 +367,7 @@ export let completionKindFromString: { }; })(); -export const enum CompletionItemKindModifier { +export const enum CompletionItemTag { Deprecated = 1 } @@ -404,7 +404,7 @@ export interface CompletionItem { * A modifier to the `kind` which affect how the item * is rendered, e.g. Deprecated is rendered with a strikeout */ - kindModifier?: Set; + tags?: ReadonlyArray; /** * A human-readable string with additional information * about this item, like type or symbol information. @@ -867,7 +867,7 @@ export const enum SymbolKind { TypeParameter = 25 } -export const enum SymbolKindTag { +export const enum SymbolTag { Deprecated = 1, } @@ -913,7 +913,7 @@ export interface DocumentSymbol { name: string; detail: string; kind: SymbolKind; - kindTags: SymbolKindTag[]; + tags: ReadonlyArray; containerName?: string; range: IRange; selectionRange: IRange; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 67cc65ca13..232b3dabfc 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -581,7 +581,7 @@ export enum CompletionItemKind { Snippet = 25 } -export enum CompletionItemKindModifier { +export enum CompletionItemTag { Deprecated = 1 } @@ -662,6 +662,6 @@ export enum SymbolKind { TypeParameter = 25 } -export enum SymbolKindTag { +export enum SymbolTag { Deprecated = 1 } \ No newline at end of file diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 66e3011e2b..ed92dc56d0 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -32,7 +32,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _currentCodeLensModel: CodeLensModel | undefined; private _modelChangeCounter: number = 0; private _currentResolveCodeLensSymbolsPromise: CancelablePromise | undefined; - private _detectVisibleLenses!: RunOnceScheduler; + private _detectVisibleLenses: RunOnceScheduler | undefined; constructor( private readonly _editor: editorBrowser.ICodeEditor, @@ -121,9 +121,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } } - this._detectVisibleLenses = new RunOnceScheduler(() => { - this._onViewportChanged(); - }, 250); + const detectVisibleLenses = this._detectVisibleLenses = new RunOnceScheduler(() => this._onViewportChanged(), 250); const scheduler = new RunOnceScheduler(() => { const counterValue = ++this._modelChangeCounter; @@ -145,12 +143,12 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // render lenses this._renderCodeLensSymbols(result); - this._detectVisibleLenses.schedule(); + detectVisibleLenses.schedule(); } }, onUnexpectedError); }, 250); this._localToDispose.add(scheduler); - this._localToDispose.add(this._detectVisibleLenses); + this._localToDispose.add(detectVisibleLenses); this._localToDispose.add(this._editor.onDidChangeModelContent(() => { this._editor.changeDecorations(decorationsAccessor => { this._editor.changeViewZones(viewZonesAccessor => { @@ -179,17 +177,17 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { }); // Compute new `visible` code lenses - this._detectVisibleLenses.schedule(); + detectVisibleLenses.schedule(); // Ask for all references again scheduler.schedule(); })); this._localToDispose.add(this._editor.onDidScrollChange(e => { if (e.scrollTopChanged && this._lenses.length > 0) { - this._detectVisibleLenses.schedule(); + detectVisibleLenses.schedule(); } })); this._localToDispose.add(this._editor.onDidLayoutChange(() => { - this._detectVisibleLenses.schedule(); + detectVisibleLenses.schedule(); })); this._localToDispose.add(toDisposable(() => { if (this._editor.getModel()) { @@ -281,7 +279,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); codeLensIndex++; groupsIndex++; } @@ -295,7 +293,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); groupsIndex++; } diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css index b8a1ce12bf..eaee13302b 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -20,11 +20,6 @@ color: var(--outline-element-color); } -.monaco-list .outline-element .deprecated { - text-decoration: line-through; - opacity: 0.66; -} - .monaco-tree .monaco-tree-row.focused .outline-element .outline-element-detail { visibility: inherit; } diff --git a/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css b/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css index 6b61c64681..0a450315ca 100644 --- a/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css +++ b/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css @@ -3,6 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-workbench .monaco-icon-label.deprecated { + text-decoration: line-through; + opacity: 0.66; +} + .monaco-workbench .symbol-icon.inline { background-position: left center; padding-left: 20px; diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index cde6226532..8e04fc2938 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -12,7 +12,7 @@ import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import 'vs/css!./media/outlineTree'; import 'vs/css!./media/symbol-icons'; import { Range } from 'vs/editor/common/core/range'; -import { SymbolKind, symbolKindToCssClass, SymbolKindTag } from 'vs/editor/common/modes'; +import { SymbolKind, symbolKindToCssClass, SymbolTag } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -127,7 +127,7 @@ export class OutlineElementRenderer implements ITreeRenderer= 0) { + if (element.symbol.tags.indexOf(SymbolTag.Deprecated) >= 0) { options.extraClasses.push(`deprecated`); options.matches = []; } diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index faa5f0955b..05503e1042 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -76,7 +76,7 @@ suite('OutlineModel', function () { name, detail: 'fake', kind: SymbolKind.Boolean, - kindTags: [], + tags: [], selectionRange: range, range: range }; diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.ts b/src/vs/editor/contrib/parameterHints/parameterHints.ts index b3038bae4f..9c8a2ca29b 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHints.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -18,7 +18,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import * as modes from 'vs/editor/common/modes'; import { TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; -class ParameterHintsController implements IEditorContribution { +class ParameterHintsController extends Disposable implements IEditorContribution { private static readonly ID = 'editor.controller.parameterHints'; @@ -30,8 +30,9 @@ class ParameterHintsController implements IEditorContribution { private readonly widget: ParameterHintsWidget; constructor(editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService) { + super(); this.editor = editor; - this.widget = instantiationService.createInstance(ParameterHintsWidget, this.editor); + this.widget = this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor)); } getId(): string { @@ -53,10 +54,6 @@ class ParameterHintsController implements IEditorContribution { trigger(context: TriggerContext): void { this.widget.trigger(context); } - - dispose(): void { - dispose(this.widget); - } } export class TriggerParameterHintsAction extends EditorAction { @@ -76,7 +73,7 @@ export class TriggerParameterHintsAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = ParameterHintsController.get(editor); + const controller = ParameterHintsController.get(editor); if (controller) { controller.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke diff --git a/src/vs/editor/contrib/quickOpen/quickOpen.ts b/src/vs/editor/contrib/quickOpen/quickOpen.ts index 4aae25223c..7b69a0970d 100644 --- a/src/vs/editor/contrib/quickOpen/quickOpen.ts +++ b/src/vs/editor/contrib/quickOpen/quickOpen.ts @@ -3,44 +3,41 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; +import { illegalArgument } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; -import { DocumentSymbol, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; +import { DocumentSymbol } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { values } from 'vs/base/common/collections'; -export function getDocumentSymbols(model: ITextModel, flat: boolean, token: CancellationToken): Promise { +export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise { - let roots: DocumentSymbol[] = []; - - let promises = DocumentSymbolProviderRegistry.all(model).map(support => { - - return Promise.resolve(support.provideDocumentSymbols(model, token)).then(result => { - if (Array.isArray(result)) { - roots.push(...result); - } - }, err => { - onUnexpectedExternalError(err); - }); - }); - - return Promise.all(promises).then(() => { - let flatEntries: DocumentSymbol[] = []; - if (token.isCancellationRequested) { - return flatEntries; - } - if (flat) { - flatten(flatEntries, roots, ''); + const model = await OutlineModel.create(document, token); + const roots: DocumentSymbol[] = []; + for (const child of values(model.children)) { + if (child instanceof OutlineElement) { + roots.push(child.symbol); } else { - flatEntries = roots; + roots.push(...values(child.children).map(child => child.symbol)); } - flatEntries.sort(compareEntriesUsingStart); + } + + let flatEntries: DocumentSymbol[] = []; + if (token.isCancellationRequested) { return flatEntries; - }); + } + if (flat) { + flatten(flatEntries, roots, ''); + } else { + flatEntries = roots; + } + + return flatEntries.sort(compareEntriesUsingStart); } function compareEntriesUsingStart(a: DocumentSymbol, b: DocumentSymbol): number { @@ -51,7 +48,7 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo for (let entry of entries) { bucket.push({ kind: entry.kind, - kindTags: [], + tags: entry.tags, name: entry.name, detail: entry.detail, containerName: entry.containerName || overrideContainerLabel, diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index ea9b05696e..24ba97bc1f 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -30,7 +30,7 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TimeoutTimer, CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; -import { CompletionItemKind, completionKindToCssClass, CompletionItemKindModifier } from 'vs/editor/common/modes'; +import { CompletionItemKind, completionKindToCssClass, CompletionItemTag } from 'vs/editor/common/modes'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -193,7 +193,7 @@ class Renderer implements IListRenderer ]; } - if (suggestion.kindModifier && suggestion.kindModifier.has(CompletionItemKindModifier.Deprecated)) { + if (suggestion.tags && suggestion.tags.indexOf(CompletionItemTag.Deprecated) >= 0) { labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['deprecated']); labelOptions.matches = []; } diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index db745880d3..e56dcf3802 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -45,6 +45,7 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platf import { ILayoutService, IDimension } from 'vs/platform/layout/browser/layoutService'; import { SimpleServicesNLS } from 'vs/editor/common/standaloneStrings'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { basename } from 'vs/base/common/resources'; export class SimpleModel implements IResolvedTextEditorModel { @@ -656,6 +657,7 @@ export class SimpleBulkEditService implements IBulkEditService { } export class SimpleUriLabelService implements ILabelService { + _serviceBrand: any; private readonly _onDidRegisterFormatter = new Emitter(); @@ -668,6 +670,10 @@ export class SimpleUriLabelService implements ILabelService { return resource.path; } + getUriBasenameLabel(resource: URI): string { + return basename(resource); + } + public getWorkspaceLabel(workspace: IWorkspaceIdentifier | URI | IWorkspace, options?: { verbose: boolean; }): string { return ''; } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 2172471a22..162a1e7bfa 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -21,7 +21,7 @@ import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/acti import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -29,6 +29,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { StandaloneCodeEditorNLS } from 'vs/editor/common/standaloneStrings'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; /** * Description of an action contribution @@ -373,7 +374,9 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @ICodeEditorService codeEditorService: ICodeEditorService, @IStandaloneThemeService themeService: IStandaloneThemeService, @INotificationService notificationService: INotificationService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IClipboardService clipboardService: IClipboardService ) { applyConfigurationValues(configurationService, options, true); options = options || {}; @@ -381,7 +384,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon options.theme = themeService.setTheme(options.theme); } - super(domElement, options, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService); + super(domElement, options, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, clipboardService); this._contextViewService = contextViewService; this._configurationService = configurationService; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 56cf187a1c..3dc783bc75 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -30,7 +30,7 @@ import { IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standal import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarker, IMarkerData } from 'vs/platform/markers/common/markers'; @@ -38,6 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; type Omit = Pick>; @@ -119,6 +120,8 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC services.get(IStandaloneThemeService), services.get(INotificationService), services.get(IConfigurationService), + services.get(IContextMenuService), + services.get(IClipboardService) ); }); } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 3859d719a7..ade01f8d33 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -562,10 +562,10 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, CompletionItemKind: standaloneEnums.CompletionItemKind, - CompletionItemKindModifier: standaloneEnums.CompletionItemKindModifier, + CompletionItemTag: standaloneEnums.CompletionItemTag, CompletionItemInsertTextRule: standaloneEnums.CompletionItemInsertTextRule, SymbolKind: standaloneEnums.SymbolKind, - SymbolKindTag: standaloneEnums.SymbolKindTag, + SymbolTag: standaloneEnums.SymbolTag, IndentAction: standaloneEnums.IndentAction, CompletionTriggerKind: standaloneEnums.CompletionTriggerKind, SignatureHelpTriggerKind: standaloneEnums.SignatureHelpTriggerKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ee5e515281..9c69908c4d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4790,7 +4790,7 @@ declare namespace monaco.languages { Snippet = 25 } - export enum CompletionItemKindModifier { + export enum CompletionItemTag { Deprecated = 1 } @@ -4826,7 +4826,7 @@ declare namespace monaco.languages { * A modifier to the `kind` which affect how the item * is rendered, e.g. Deprecated is rendered with a strikeout */ - kindModifier?: Set; + tags?: ReadonlyArray; /** * A human-readable string with additional information * about this item, like type or symbol information. @@ -5236,7 +5236,7 @@ declare namespace monaco.languages { TypeParameter = 25 } - export enum SymbolKindTag { + export enum SymbolTag { Deprecated = 1 } @@ -5244,7 +5244,7 @@ declare namespace monaco.languages { name: string; detail: string; kind: SymbolKind; - kindTags: SymbolKindTag[]; + tags: ReadonlyArray; containerName?: string; range: IRange; selectionRange: IRange; diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index d65ac31202..1009bcd3f2 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -21,6 +21,7 @@ export interface ILabelService { * If noPrefix is passed does not tildify the label and also does not prepand the root name for relative labels in a multi root scenario. */ getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean }): string; + getUriBasenameLabel(resource: URI): string; getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string; getHostLabel(scheme: string, authority?: string): string; getSeparator(scheme: string, authority?: string): '/' | '\\'; diff --git a/src/vs/platform/lifecycle/browser/lifecycleService.ts b/src/vs/platform/lifecycle/browser/lifecycleService.ts index 0ace697ee8..3401a44801 100644 --- a/src/vs/platform/lifecycle/browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/browser/lifecycleService.ts @@ -8,7 +8,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; import { localize } from 'vs/nls'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; export class BrowserLifecycleService extends AbstractLifecycleService { @@ -23,7 +22,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { } private registerListeners(): void { - addDisposableListener(window, EventType.BEFORE_UNLOAD, () => this.onBeforeUnload()); + // Note: we cannot change this to window.addEventListener('beforeUnload') + // because it seems that mechanism does not allow for preventing the unload + window.onbeforeunload = () => this.onBeforeUnload(); } private onBeforeUnload(): string | null { diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 2a75ea306e..639b35ccc1 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -89,7 +89,6 @@ export interface IProductConfiguration { readonly 'linux-x64': string; readonly 'darwin': string; }; - readonly logUploaderUrl: string; readonly portable?: string; readonly uiExtensions?: readonly string[]; } diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index e1b2407f32..b910f899bb 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -10,9 +10,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { ISignService } from 'vs/platform/sign/common/sign'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { ILogService } from 'vs/platform/log/common/log'; export const enum ConnectionType { Management = 1, @@ -20,6 +21,17 @@ export const enum ConnectionType { Tunnel = 3, } +function connectionTypeToString(connectionType: ConnectionType): string { + switch (connectionType) { + case ConnectionType.Management: + return 'Management'; + case ConnectionType.ExtensionHost: + return 'ExtensionHost'; + case ConnectionType.Tunnel: + return 'Tunnel'; + } +} + export interface AuthRequest { type: 'auth'; auth: string; @@ -58,6 +70,7 @@ interface ISimpleConnectionOptions { reconnectionProtocol: PersistentProtocol | null; socketFactory: ISocketFactory; signService: ISignService; + logService: ILogService; } export interface IConnectCallback { @@ -68,29 +81,46 @@ export interface ISocketFactory { connect(host: string, port: number, query: string, callback: IConnectCallback): void; } -async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise { - const protocol = await new Promise((c, e) => { +async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> { + const logPrefix = connectLogPrefix(options, connectionType); + const { protocol, ownsProtocol } = await new Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }>((c, e) => { + options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`); options.socketFactory.connect( options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, (err: any, socket: ISocket) => { if (err) { + options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); + options.logService.error(err); e(err); return; } + options.logService.trace(`${logPrefix} 2/6. socketFactory.connect() was successful.`); if (options.reconnectionProtocol) { options.reconnectionProtocol.beginAcceptReconnection(socket, null); - c(options.reconnectionProtocol); + c({ protocol: options.reconnectionProtocol, ownsProtocol: false }); } else { - c(new PersistentProtocol(socket, null)); + c({ protocol: new PersistentProtocol(socket, null), ownsProtocol: true }); } } ); }); - return new Promise((c, e) => { + return new Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }>((c, e) => { + + const errorTimeoutToken = setTimeout(() => { + const error: any = new Error('handshake timeout'); + error.code = 'ETIMEDOUT'; + error.syscall = 'connect'; + options.logService.error(`${logPrefix} the handshake took longer than 10 seconds. Error:`); + options.logService.error(error); + if (ownsProtocol) { + safeDisposeProtocolAndSocket(protocol); + } + e(error); + }, 10000); const messageRegistration = protocol.onControlMessage(async raw => { const msg = JSON.parse(raw.toString()); @@ -99,11 +129,16 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio const error = getErrorFromMessage(msg); if (error) { + options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`); + options.logService.error(error); + if (ownsProtocol) { + safeDisposeProtocolAndSocket(protocol); + } return e(error); } if (msg.type === 'sign') { - + options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`); const signed = await options.signService.sign(msg.data); const connTypeRequest: ConnectionTypeRequest = { type: 'connectionType', @@ -114,17 +149,22 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio if (args) { connTypeRequest.args = args; } + options.logService.trace(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`); protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest))); - c(protocol); + clearTimeout(errorTimeoutToken); + c({ protocol, ownsProtocol }); } else { - e(new Error('handshake error')); + const error = new Error('handshake error'); + options.logService.error(`${logPrefix} received unexpected control message. Error:`); + options.logService.error(error); + if (ownsProtocol) { + safeDisposeProtocolAndSocket(protocol); + } + e(error); } }); - setTimeout(() => { - e(new Error('handshake timeout')); - }, 2000); - + options.logService.trace(`${logPrefix} 3/6. sending AuthRequest control message.`); // TODO@vs-remote: use real nonce here const authRequest: AuthRequest = { type: 'auth', @@ -138,24 +178,37 @@ interface IManagementConnectionResult { protocol: PersistentProtocol; } -async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions): Promise { - const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.Management, undefined); - return new Promise((c, e) => { +async function connectToRemoteExtensionHostAgentAndReadOneMessage(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; firstMessage: any }> { + const startTime = Date.now(); + const logPrefix = connectLogPrefix(options, connectionType); + const { protocol, ownsProtocol } = await connectToRemoteExtensionHostAgent(options, connectionType, args); + return new Promise<{ protocol: PersistentProtocol; firstMessage: any }>((c, e) => { const registration = protocol.onControlMessage(raw => { registration.dispose(); const msg = JSON.parse(raw.toString()); const error = getErrorFromMessage(msg); if (error) { + options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`); + options.logService.error(error); + if (ownsProtocol) { + safeDisposeProtocolAndSocket(protocol); + } return e(error); } if (options.reconnectionProtocol) { options.reconnectionProtocol.endAcceptReconnection(); } - c({ protocol }); + options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`); + c({ protocol, firstMessage: msg }); }); }); } +async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions): Promise { + const { protocol } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.Management, undefined); + return { protocol }; +} + export interface IRemoteExtensionHostStartParams { language: string; debugId?: string; @@ -170,22 +223,9 @@ interface IExtensionHostConnectionResult { } async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise { - const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.ExtensionHost, startArguments); - return new Promise((c, e) => { - const registration = protocol.onControlMessage(raw => { - registration.dispose(); - const msg = JSON.parse(raw.toString()); - const error = getErrorFromMessage(msg); - if (error) { - return e(error); - } - const debugPort = msg && msg.debugPort; - if (options.reconnectionProtocol) { - options.reconnectionProtocol.endAcceptReconnection(); - } - c({ protocol, debugPort }); - }); - }); + const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.ExtensionHost, startArguments); + const debugPort = firstMessage && firstMessage.debugPort; + return { protocol, debugPort }; } export interface ITunnelConnectionStartParams { @@ -193,7 +233,10 @@ export interface ITunnelConnectionStartParams { } async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, startParams: ITunnelConnectionStartParams): Promise { - const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams); + const startTime = Date.now(); + const logPrefix = connectLogPrefix(options, ConnectionType.Tunnel); + const { protocol } = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams); + options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`); return protocol; } @@ -202,6 +245,7 @@ export interface IConnectionOptions { socketFactory: ISocketFactory; addressProvider: IAddressProvider; signService: ISignService; + logService: ILogService; } async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise { @@ -213,7 +257,8 @@ async function resolveConnectionOptions(options: IConnectionOptions, reconnectio reconnectionToken: reconnectionToken, reconnectionProtocol: reconnectionProtocol, socketFactory: options.socketFactory, - signService: options.signService + signService: options.signService, + logService: options.logService }; } @@ -227,22 +272,36 @@ export interface IAddressProvider { } export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise { - const reconnectionToken = generateUuid(); - const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); - const { protocol } = await doConnectRemoteAgentManagement(simpleOptions); - return new ManagementPersistentConnection(options, remoteAuthority, clientId, reconnectionToken, protocol); + try { + const reconnectionToken = generateUuid(); + const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); + const { protocol } = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentManagement(simpleOptions), 30 * 1000 /*30s*/); + return new ManagementPersistentConnection(options, remoteAuthority, clientId, reconnectionToken, protocol); + } catch (err) { + options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`); + options.logService.error(err); + PersistentConnection.triggerPermanentFailure(); + throw err; + } } export async function connectRemoteAgentExtensionHost(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise { - const reconnectionToken = generateUuid(); - const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); - const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(simpleOptions, startArguments); - return new ExtensionHostPersistentConnection(options, startArguments, reconnectionToken, protocol, debugPort); + try { + const reconnectionToken = generateUuid(); + const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); + const { protocol, debugPort } = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentExtensionHost(simpleOptions, startArguments), 30 * 1000 /*30s*/); + return new ExtensionHostPersistentConnection(options, startArguments, reconnectionToken, protocol, debugPort); + } catch (err) { + options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`); + options.logService.error(err); + PersistentConnection.triggerPermanentFailure(); + throw err; + } } export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { const simpleOptions = await resolveConnectionOptions(options, generateUuid(), null); - const protocol = await doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort }); + const protocol = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort }), 30 * 1000 /*30s*/); return protocol; } @@ -292,6 +351,13 @@ export type PersistenConnectionEvent = ConnectionGainEvent | ConnectionLostEvent abstract class PersistentConnection extends Disposable { + public static triggerPermanentFailure(): void { + this._permanentFailure = true; + this._instances.forEach(instance => instance._gotoPermanentFailure()); + } + private static _permanentFailure: boolean = false; + private static _instances: PersistentConnection[] = []; + private readonly _onDidStateChange = this._register(new Emitter()); public readonly onDidStateChange = this._onDidStateChange.event; @@ -300,20 +366,24 @@ abstract class PersistentConnection extends Disposable { public readonly protocol: PersistentProtocol; private _isReconnecting: boolean; - private _permanentFailure: boolean; - constructor(options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) { + constructor(private readonly _connectionType: ConnectionType, options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) { super(); this._options = options; this.reconnectionToken = reconnectionToken; this.protocol = protocol; this._isReconnecting = false; - this._permanentFailure = false; this._onDidStateChange.fire(new ConnectionGainEvent()); this._register(protocol.onSocketClose(() => this._beginReconnecting())); this._register(protocol.onSocketTimeout(() => this._beginReconnecting())); + + PersistentConnection._instances.push(this); + + if (PersistentConnection._permanentFailure) { + this._gotoPermanentFailure(); + } } private async _beginReconnecting(): Promise { @@ -330,10 +400,12 @@ abstract class PersistentConnection extends Disposable { } private async _runReconnectingLoop(): Promise { - if (this._permanentFailure) { + if (PersistentConnection._permanentFailure) { // no more attempts! return; } + const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true); + this._options.logService.info(`${logPrefix} starting reconnecting loop. You can get more information with the trace log level.`); this._onDidStateChange.fire(new ConnectionLostEvent()); const TIMES = [5, 5, 10, 10, 10, 10, 10, 30]; const disconnectStartTime = Date.now(); @@ -345,59 +417,68 @@ abstract class PersistentConnection extends Disposable { const sleepPromise = sleep(waitTime); this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise)); + this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`); try { await sleepPromise; } catch { } // User canceled timer + if (PersistentConnection._permanentFailure) { + this._options.logService.error(`${logPrefix} permanent failure occurred while running the reconnecting loop.`); + break; + } + // connection was lost, let's try to re-establish it this._onDidStateChange.fire(new ReconnectionRunningEvent()); + this._options.logService.info(`${logPrefix} resolving connection...`); const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol); - await connectWithTimeLimit(this._reconnect(simpleOptions), 30 * 1000 /*30s*/); + this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.host}:${simpleOptions.port}...`); + await connectWithTimeLimit(simpleOptions.logService, this._reconnect(simpleOptions), 30 * 1000 /*30s*/); + this._options.logService.info(`${logPrefix} reconnected!`); this._onDidStateChange.fire(new ConnectionGainEvent()); break; } catch (err) { if (err.code === 'VSCODE_CONNECTION_ERROR') { - console.error(`A permanent connection error occurred`); - console.error(err); - this._permanentFailure = true; - this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); - this.protocol.acceptDisconnect(); + this._options.logService.error(`${logPrefix} A permanent error occurred in the reconnecting loop! Will give up now! Error:`); + this._options.logService.error(err); + PersistentConnection.triggerPermanentFailure(); break; } if (Date.now() - disconnectStartTime > ProtocolConstants.ReconnectionGraceTime) { - console.error(`Giving up after reconnection grace time has expired!`); - this._permanentFailure = true; - this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); - this.protocol.acceptDisconnect(); + this._options.logService.error(`${logPrefix} An error occurred while reconnecting, but it will be treated as a permanent error because the reconnection grace time has expired! Will give up now! Error:`); + this._options.logService.error(err); + PersistentConnection.triggerPermanentFailure(); break; } if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) { - console.warn(`A temporarily not available error occured while trying to reconnect:`); - console.warn(err); + this._options.logService.info(`${logPrefix} A temporarily not available error occured while trying to reconnect, will try again...`); + this._options.logService.trace(err); // try again! continue; } if ((err.code === 'ETIMEDOUT' || err.code === 'ENETUNREACH' || err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') && err.syscall === 'connect') { - console.warn(`A connect error occured while trying to reconnect:`); - console.warn(err); + this._options.logService.info(`${logPrefix} A network error occured while trying to reconnect, will try again...`); + this._options.logService.trace(err); // try again! continue; } if (isPromiseCanceledError(err)) { - console.warn(`A cancel error occured while trying to reconnect:`); - console.warn(err); + this._options.logService.info(`${logPrefix} A promise cancelation error occured while trying to reconnect, will try again...`); + this._options.logService.trace(err); // try again! continue; } - console.error(`An error occured while trying to reconnect:`); - console.error(err); - this._permanentFailure = true; - this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); - this.protocol.acceptDisconnect(); + this._options.logService.error(`${logPrefix} An unknown error occured while trying to reconnect, since this is an unknown case, it will be treated as a permanent error! Will give up now! Error:`); + this._options.logService.error(err); + PersistentConnection.triggerPermanentFailure(); break; } - } while (!this._permanentFailure); + } while (!PersistentConnection._permanentFailure); + } + + private _gotoPermanentFailure(): void { + this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); + safeDisposeProtocolAndSocket(this.protocol); } protected abstract _reconnect(options: ISimpleConnectionOptions): Promise; @@ -408,7 +489,7 @@ export class ManagementPersistentConnection extends PersistentConnection { public readonly client: Client; constructor(options: IConnectionOptions, remoteAuthority: string, clientId: string, reconnectionToken: string, protocol: PersistentProtocol) { - super(options, reconnectionToken, protocol); + super(ConnectionType.Management, options, reconnectionToken, protocol); this.client = this._register(new Client(protocol, { remoteAuthority: remoteAuthority, clientId: clientId @@ -426,7 +507,7 @@ export class ExtensionHostPersistentConnection extends PersistentConnection { public readonly debugPort: number | undefined; constructor(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) { - super(options, reconnectionToken, protocol); + super(ConnectionType.ExtensionHost, options, reconnectionToken, protocol); this._startArguments = startArguments; this.debugPort = debugPort; } @@ -436,17 +517,19 @@ export class ExtensionHostPersistentConnection extends PersistentConnection { } } -function connectWithTimeLimit(p: Promise, timeLimit: number): Promise { - return new Promise((resolve, reject) => { +function connectWithTimeLimit(logService: ILogService, p: Promise, timeLimit: number): Promise { + return new Promise((resolve, reject) => { let timeout = setTimeout(() => { const err: any = new Error('Time limit reached'); err.code = 'ETIMEDOUT'; err.syscall = 'connect'; + logService.error(`[remote-connection] The time limit has been reached for a connection. Error:`); + logService.error(err); reject(err); }, timeLimit); - p.then(() => { + p.then((value) => { clearTimeout(timeout); - resolve(); + resolve(value); }, (err) => { clearTimeout(timeout); reject(err); @@ -454,6 +537,17 @@ function connectWithTimeLimit(p: Promise, timeLimit: number): Promise; + } + + export interface DocumentSymbol { + /** + * + */ + tags?: ReadonlyArray; + } + + export enum CompletionItemTag { Deprecated = 1 } @@ -1149,7 +1167,7 @@ declare module 'vscode' { /** * */ - kind2?: CompletionItemKind | { base: CompletionItemKind, modifier: ReadonlyArray }; + tags?: ReadonlyArray; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 9f3b74b223..bf2f9fd7f1 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -21,7 +21,6 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { mixin } from 'vs/base/common/objects'; -import { fromArray } from 'vs/base/common/map'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -331,7 +330,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { label: data.a, kind: data.b, - kindModifier: data.n && fromArray(data.n), + tags: data.n, detail: data.c, documentation: data.d, sortText: data.e, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 809988add1..0dbfb20f78 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -544,9 +544,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }; // namespace: workspace + let warnedRootPathDeprecated = false; const workspace: typeof vscode.workspace = { get rootPath() { - console.warn(`[Deprecation Warning] 'workspace.rootPath' is deprecated and should no longer be used. Please use 'workspace.workspaceFolders' instead. (${extension.publisher}.${extension.name})`); + if (extension.isUnderDevelopment && !warnedRootPathDeprecated) { + warnedRootPathDeprecated = true; + console.warn(`[Deprecation Warning] 'workspace.rootPath' is deprecated and should no longer be used. Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`); + } + return extHostWorkspace.getPath(); }, set rootPath(value) { @@ -815,7 +820,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CommentMode: extHostTypes.CommentMode, CompletionItem: extHostTypes.CompletionItem, CompletionItemKind: extHostTypes.CompletionItemKind, - CompletionItemKindModifier: extHostTypes.CompletionItemKindModifier, + CompletionItemTag: extHostTypes.CompletionItemTag, CompletionList: extHostTypes.CompletionList, CompletionTriggerKind: extHostTypes.CompletionTriggerKind, ConfigurationTarget: extHostTypes.ConfigurationTarget, @@ -870,6 +875,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I StatusBarAlignment: extHostTypes.StatusBarAlignment, SymbolInformation: extHostTypes.SymbolInformation, SymbolKind: extHostTypes.SymbolKind, + SymbolTag: extHostTypes.SymbolTag, Task: extHostTypes.Task, Task2: extHostTypes.Task, TaskGroup: extHostTypes.TaskGroup, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d96d7133aa..d80c87c874 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -940,7 +940,7 @@ export interface ISuggestDataDto { k/* commitCharacters */?: string[]; l/* additionalTextEdits */?: ISingleEditOperation[]; m/* command */?: modes.Command; - n/* kindModifier */?: modes.CompletionItemKindModifier[]; + n/* kindModifier */?: modes.CompletionItemTag[]; // not-standard x?: ChainedCacheId; } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 4ace0760c7..604a6a2663 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -70,8 +70,8 @@ class DocumentSymbolAdapter { const element = { name: info.name || '!!MISSING: name!!', kind: typeConvert.SymbolKind.from(info.kind), - kindTags: [], - detail: undefined!, // Strict null override — avoid changing behavior + tags: info.tags && info.tags.map(typeConvert.SymbolTag.from), + detail: '', containerName: info.containerName, range: typeConvert.Range.from(info.location.range), selectionRange: typeConvert.Range.from(info.location.range), @@ -728,6 +728,7 @@ class SuggestAdapter { // a: item.label, b: typeConvert.CompletionItemKind.from(item.kind), + n: item.tags && item.tags.map(typeConvert.CompletionItemTag.from), c: item.detail, d: typeof item.documentation === 'undefined' ? undefined : typeConvert.MarkdownString.fromStrict(item.documentation), e: item.sortText, @@ -739,12 +740,6 @@ class SuggestAdapter { m: this._commands.toInternal(item.command, disposables), }; - // kind2 - if (typeof item.kind2 === 'object') { - result.b = typeConvert.CompletionItemKind.from(item.kind2.base); - result.n = item.kind2.modifier.map(typeConvert.CompletionItemKindModifier.from); - } - // 'insertText'-logic if (item.textEdit) { result.h = item.textEdit.newText; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index dca646dc85..ae3ab219eb 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -28,7 +28,7 @@ import * as marked from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { cloneAndChange } from 'vs/base/common/objects'; import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; -import { coalesce } from 'vs/base/common/arrays'; +import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; export interface PositionLike { @@ -556,22 +556,40 @@ export namespace SymbolKind { } } +export namespace SymbolTag { + + export function from(kind: types.SymbolTag): modes.SymbolTag { + switch (kind) { + case types.SymbolTag.Deprecated: return modes.SymbolTag.Deprecated; + } + } + + export function to(kind: modes.SymbolTag): types.SymbolTag { + switch (kind) { + case modes.SymbolTag.Deprecated: return types.SymbolTag.Deprecated; + } + } +} + export namespace WorkspaceSymbol { export function from(info: vscode.SymbolInformation): search.IWorkspaceSymbol { return { name: info.name, kind: SymbolKind.from(info.kind), + tags: info.tags && info.tags.map(SymbolTag.from), containerName: info.containerName, location: location.from(info.location) }; } export function to(info: search.IWorkspaceSymbol): types.SymbolInformation { - return new types.SymbolInformation( + const result = new types.SymbolInformation( info.name, SymbolKind.to(info.kind), info.containerName, location.to(info.location) ); + result.tags = info.tags && info.tags.map(SymbolTag.to); + return result; } } @@ -583,7 +601,7 @@ export namespace DocumentSymbol { range: Range.from(info.range), selectionRange: Range.from(info.selectionRange), kind: SymbolKind.from(info.kind), - kindTags: [] + tags: info.tags ? info.tags.map(SymbolTag.from) : [] }; if (info.children) { result.children = info.children.map(from); @@ -598,6 +616,9 @@ export namespace DocumentSymbol { Range.to(info.range), Range.to(info.selectionRange), ); + if (isNonEmptyArray(info.tags)) { + result.tags = info.tags.map(SymbolTag.to); + } if (info.children) { result.children = info.children.map(to) as any; } @@ -682,17 +703,17 @@ export namespace CompletionContext { } } -export namespace CompletionItemKindModifier { +export namespace CompletionItemTag { - export function from(kind: types.CompletionItemKindModifier): modes.CompletionItemKindModifier { + export function from(kind: types.CompletionItemTag): modes.CompletionItemTag { switch (kind) { - case types.CompletionItemKindModifier.Deprecated: return modes.CompletionItemKindModifier.Deprecated; + case types.CompletionItemTag.Deprecated: return modes.CompletionItemTag.Deprecated; } } - export function to(kind: modes.CompletionItemKindModifier): types.CompletionItemKindModifier { + export function to(kind: modes.CompletionItemTag): types.CompletionItemTag { switch (kind) { - case modes.CompletionItemKindModifier.Deprecated: return types.CompletionItemKindModifier.Deprecated; + case modes.CompletionItemTag.Deprecated: return types.CompletionItemTag.Deprecated; } } } @@ -768,6 +789,7 @@ export namespace CompletionItem { const result = new types.CompletionItem(suggestion.label); result.insertText = suggestion.insertText; result.kind = CompletionItemKind.to(suggestion.kind); + result.tags = suggestion.tags && suggestion.tags.map(CompletionItemTag.to); result.detail = suggestion.detail; result.documentation = htmlContent.isMarkdownString(suggestion.documentation) ? MarkdownString.to(suggestion.documentation) : suggestion.documentation; result.sortText = suggestion.sortText; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index ebc275eac0..40530aa1fe 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -979,6 +979,10 @@ export enum SymbolKind { TypeParameter = 25 } +export enum SymbolTag { + Deprecated = 1, +} + @es5ClassCompat export class SymbolInformation { @@ -991,6 +995,7 @@ export class SymbolInformation { name: string; location!: Location; kind: SymbolKind; + tags?: SymbolTag[]; containerName: string | undefined; constructor(name: string, kind: SymbolKind, containerName: string | undefined, location: Location); @@ -1041,6 +1046,7 @@ export class DocumentSymbol { name: string; detail: string; kind: SymbolKind; + tags?: SymbolTag[]; range: Range; selectionRange: Range; children: DocumentSymbol[]; @@ -1308,7 +1314,7 @@ export enum CompletionItemKind { TypeParameter = 24 } -export enum CompletionItemKindModifier { +export enum CompletionItemTag { Deprecated = 1, } @@ -1317,7 +1323,7 @@ export class CompletionItem implements vscode.CompletionItem { label: string; kind?: CompletionItemKind; - kind2?: CompletionItemKind | { base: CompletionItemKind, modifier: CompletionItemKindModifier[] }; + tags?: CompletionItemTag[]; detail?: string; documentation?: string | MarkdownString; sortText?: string; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 169fc7f604..c69754526d 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -299,7 +299,24 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } public $onDidChangeWebviewPanelViewStates(newStates: WebviewPanelViewStateData): void { - for (const handle of Object.keys(newStates)) { + const handles = Object.keys(newStates); + // Notify webviews of state changes in the following order: + // - Non-visible + // - Visible + // - Active + handles.sort((a, b) => { + const stateA = newStates[a]; + const stateB = newStates[b]; + if (stateA.active) { + return 1; + } + if (stateB.active) { + return -1; + } + return (+stateA.visible) - (+stateB.visible); + }); + + for (const handle of handles) { const panel = this.getWebviewPanel(handle); if (!panel || panel._isDisposed) { continue; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 245a684391..161525981e 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -40,7 +40,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; -import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { FileLogService } from 'vs/platform/log/common/fileLogService'; import { toLocalISOString } from 'vs/base/common/date'; @@ -90,7 +89,7 @@ class CodeRendererMain extends Disposable { // Driver if (this.configuration.driver) { - registerWindowDriver().then(d => this._register(d)); + (async () => this._register(await registerWindowDriver()))(); } // Startup @@ -144,7 +143,7 @@ class CodeRendererMain extends Disposable { serviceCollection.set(ISignService, signService); // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService)); + const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); // Files @@ -153,23 +152,24 @@ class CodeRendererMain extends Disposable { // Logger const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme); - indexedDBLogProvider.database.then( - () => fileService.registerProvider(logsPath.scheme, indexedDBLogProvider), - e => { + (async () => { + try { + await indexedDBLogProvider.database; + + fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); + } catch (error) { (logService).info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); - (logService).error(e); + (logService).error(error); + fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); } - ).then(() => { + const consoleLogService = new ConsoleLogService(logService.getLevel()); const fileLogService = new FileLogService('window', environmentService.logFile, logService.getLevel(), fileService); logService.logger = new MultiplexLogService([consoleLogService, fileLogService]); - }); - - // Static Extensions - const staticExtensions = new StaticExtensionsService(this.configuration.staticExtensions || []); - serviceCollection.set(IStaticExtensionsService, staticExtensions); + })(); + // User Data Provider let userDataProvider: IFileSystemProvider | undefined = this.configuration.userDataProvider; const connection = remoteAgentService.getConnection(); if (connection) { @@ -185,14 +185,12 @@ class CodeRendererMain extends Disposable { } } } - if (!userDataProvider) { userDataProvider = this._register(new InMemoryUserDataProvider()); } - - // User Data Provider fileService.registerProvider(Schemas.userData, userDataProvider); + // Long running services (workspace, config, storage) const services = await Promise.all([ this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => { diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 99c3fc0b57..1a452f0e8a 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -7,7 +7,6 @@ import { URI } from 'vs/base/common/uri'; import { suggestFilename } from 'vs/base/common/mime'; import { memoize } from 'vs/base/common/decorators'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { basename } from 'vs/base/common/path'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; @@ -64,7 +63,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport @memoize private get shortDescription(): string { - return basename(this.labelService.getUriLabel(dirname(this.resource))); + return this.labelService.getUriBasenameLabel(dirname(this.resource)); } @memoize diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index c6bbc1018b..66b8162b4c 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -170,7 +170,11 @@ export class FilesRenderer implements ITreeRenderer { - this.updateWidth(stat); + try { + this.updateWidth(stat); + } catch (e) { + // noop since the element might no longer be in the tree, no update of width necessery + } }); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 0fc86ca123..1f6628b224 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -5,7 +5,6 @@ import { localize } from 'vs/nls'; import { memoize } from 'vs/base/common/decorators'; -import { basename } from 'vs/base/common/path'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; @@ -147,14 +146,14 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { getName(): string { if (!this.name) { - this.name = basename(this.labelService.getUriLabel(this.resource)); + this.name = this.labelService.getUriBasenameLabel(this.resource); } return this.decorateLabel(this.name); } private get shortDescription(): string { - return basename(this.labelService.getUriLabel(dirname(this.resource))); + return this.labelService.getUriBasenameLabel(dirname(this.resource)); } private get mediumDescription(): string { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 79d83c6f5d..ea9ba2e522 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -37,7 +37,7 @@ suite('Files - FileEditorInput', () => { accessor = instantiationService.createInstance(ServiceAccessor); }); - test('Basics', async function () { + test.skip('Basics', async function () { // {{SQL CARBON EDIT}} skip test let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined); const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined, undefined); const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined, undefined); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index 0a95a958a3..f6d56d2702 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -16,15 +16,15 @@ import { IModelDecorationsChangeAccessor, OverviewRulerLane, IModelDeltaDecorati import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; -import { DocumentSymbolProviderRegistry, DocumentSymbol, symbolKindToCssClass, SymbolKind } from 'vs/editor/common/modes'; +import { DocumentSymbolProviderRegistry, DocumentSymbol, symbolKindToCssClass, SymbolKind, SymbolTag } from 'vs/editor/common/modes'; import { IRange } from 'vs/editor/common/core/range'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; import { GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { asPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; export const GOTO_SYMBOL_PREFIX = '@'; export const SCOPE_PREFIX = ':'; @@ -73,10 +73,8 @@ class OutlineModel extends QuickOpenModel { applyFilter(searchValue: string): void { // Normalize search - let normalizedSearchValue = searchValue; - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - normalizedSearchValue = normalizedSearchValue.substr(SCOPE_PREFIX.length); - } + const searchValueLow = searchValue.toLowerCase(); + const searchValuePos = searchValue.indexOf(SCOPE_PREFIX) === 0 ? 1 : 0; // Check for match and update visibility and group label this.entries.forEach((entry: SymbolEntry) => { @@ -84,34 +82,24 @@ class OutlineModel extends QuickOpenModel { // Clear all state first entry.setGroupLabel(undefined); entry.setShowBorder(false); - entry.setHighlights([]); + entry.setScore(undefined); entry.setHidden(false); // Filter by search - if (normalizedSearchValue) { - const highlights = filters.matchesFuzzy2(normalizedSearchValue, entry.getLabel()); - if (highlights) { - entry.setHighlights(highlights); - entry.setHidden(false); - } else if (!entry.isHidden()) { - entry.setHidden(true); - } + if (searchValue.length > searchValuePos) { + const score = filters.fuzzyScore( + searchValue.substr(searchValuePos), searchValueLow.substr(searchValuePos), 0, + entry.getLabel(), entry.getLabel().toLowerCase(), 0, + true + ); + entry.setScore(score); + entry.setHidden(!score); } }); - // Sort properly if actually searching - if (searchValue) { - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - this.entries.sort(this.sortScoped.bind(this, searchValue.toLowerCase())); - } else { - this.entries.sort(this.sortNormal.bind(this, searchValue.toLowerCase())); - } - } + this.entries.sort(SymbolEntry.compareByRank); + - // Otherwise restore order as appearing in outline - else { - this.entries.sort((a: SymbolEntry, b: SymbolEntry) => a.getIndex() - b.getIndex()); - } // Mark all type groups const visibleResults = this.getEntries(true); @@ -156,74 +144,6 @@ class OutlineModel extends QuickOpenModel { } } - private sortNormal(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - - // Handle hidden elements - if (elementA.isHidden() && elementB.isHidden()) { - return 0; - } else if (elementA.isHidden()) { - return 1; - } else if (elementB.isHidden()) { - return -1; - } - - const elementAName = elementA.getLabel().toLowerCase(); - const elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - const r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - - // If name identical sort by range instead - const elementARange = elementA.getRange(); - const elementBRange = elementB.getRange(); - - return elementARange.startLineNumber - elementBRange.startLineNumber; - } - - private sortScoped(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - - // Handle hidden elements - if (elementA.isHidden() && elementB.isHidden()) { - return 0; - } else if (elementA.isHidden()) { - return 1; - } else if (elementB.isHidden()) { - return -1; - } - - // Remove scope char - searchValue = searchValue.substr(SCOPE_PREFIX.length); - - // Sort by type first if scoped search - const elementATypeLabel = NLS_SYMBOL_KIND_CACHE[elementA.getKind()] || FALLBACK_NLS_SYMBOL_KIND; - const elementBTypeLabel = NLS_SYMBOL_KIND_CACHE[elementB.getKind()] || FALLBACK_NLS_SYMBOL_KIND; - let r = elementATypeLabel.localeCompare(elementBTypeLabel); - if (r !== 0) { - return r; - } - - // Special sort when searching in scoped mode - if (searchValue) { - const elementAName = elementA.getLabel().toLowerCase(); - const elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - } - - // Default to sort by range - const elementARange = elementA.getRange(); - const elementBRange = elementB.getRange(); - - return elementARange.startLineNumber - elementBRange.startLineNumber; - } - private renderGroupLabel(type: SymbolKind, count: number): string { let pattern = NLS_SYMBOL_KIND_CACHE[type]; if (!pattern) { @@ -235,33 +155,26 @@ class OutlineModel extends QuickOpenModel { } class SymbolEntry extends EditorQuickOpenEntryGroup { - private editorService: IEditorService; - private index: number; - private name: string; - private kind: SymbolKind; - private icon: string; - private description: string; - private range: IRange; - private revealRange: IRange; - private handler: GotoSymbolHandler; - constructor(index: number, name: string, kind: SymbolKind, description: string, icon: string, range: IRange, revealRange: IRange, highlights: IHighlight[], editorService: IEditorService, handler: GotoSymbolHandler) { + private score?: filters.FuzzyScore; + + constructor( + private readonly index: number, + private readonly name: string, + private readonly kind: SymbolKind, + private readonly description: string, + private readonly icon: string, + private readonly deprecated: boolean, + private readonly range: IRange, + private readonly revealRange: IRange, + private readonly editorService: IEditorService, + private readonly handler: GotoSymbolHandler + ) { super(); - - this.index = index; - this.name = name; - this.kind = kind; - this.icon = icon; - this.description = description; - this.range = range; - this.revealRange = revealRange || range; - this.setHighlights(highlights); - this.editorService = editorService; - this.handler = handler; } - getIndex(): number { - return this.index; + setScore(score: filters.FuzzyScore | undefined): void { + this.score = score; } getLabel(): string { @@ -276,6 +189,18 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { return this.icon; } + getLabelOptions(): IIconLabelValueOptions | undefined { + return this.deprecated ? { extraClasses: ['deprecated'] } : undefined; + } + + getHighlights(): [IHighlight[], IHighlight[] | undefined, IHighlight[] | undefined] { + return [ + this.deprecated ? [] : filters.createMatches(this.score), + undefined, + undefined + ]; + } + getDescription(): string { return this.description; } @@ -294,7 +219,7 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { getOptions(pinned?: boolean): ITextEditorOptions { return { - selection: this.toSelection(), + selection: this.revealRange, pinned }; } @@ -317,7 +242,7 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { // Apply selection and focus else { - const range = this.toSelection(); + const range = this.revealRange; const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (activeTextEditorWidget) { activeTextEditorWidget.setSelection(range); @@ -331,7 +256,7 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { private runPreview(): boolean { // Select Outline Position - const range = this.toSelection(); + const range = this.revealRange; const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (activeTextEditorWidget) { activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); @@ -345,13 +270,36 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { return false; } - private toSelection(): IRange { - return { - startLineNumber: this.revealRange.startLineNumber, - startColumn: this.revealRange.startColumn || 1, - endLineNumber: this.revealRange.startLineNumber, - endColumn: this.revealRange.startColumn || 1 - }; + static compareByRank(a: SymbolEntry, b: SymbolEntry): number { + if (!a.score && b.score) { + return 1; + } else if (a.score && !b.score) { + return -1; + } + if (a.score && b.score) { + if (a.score[0] > b.score[0]) { + return -1; + } else if (a.score[0] < b.score[0]) { + return 1; + } + } + if (a.index < b.index) { + return -1; + } else if (a.index > b.index) { + return 1; + } + return 0; + } + + static compareByKindAndRank(a: SymbolEntry, b: SymbolEntry): number { + // Sort by type first if scoped search + const kindA = NLS_SYMBOL_KIND_CACHE[a.getKind()] || FALLBACK_NLS_SYMBOL_KIND; + const kindB = NLS_SYMBOL_KIND_CACHE[b.getKind()] || FALLBACK_NLS_SYMBOL_KIND; + let r = kindA.localeCompare(kindB); + if (r === 0) { + r = this.compareByRank(a, b); + } + return r; } } @@ -466,11 +414,11 @@ export class GotoSymbolHandler extends QuickOpenHandler { }; } - private toQuickOpenEntries(flattened: DocumentSymbol[]): SymbolEntry[] { + private toQuickOpenEntries(symbols: DocumentSymbol[]): SymbolEntry[] { const results: SymbolEntry[] = []; - for (let i = 0; i < flattened.length; i++) { - const element = flattened[i]; + for (let i = 0; i < symbols.length; i++) { + const element = symbols[i]; const label = strings.trim(element.name); // Show parent scope as description @@ -479,8 +427,8 @@ export class GotoSymbolHandler extends QuickOpenHandler { // Add results.push(new SymbolEntry(i, - label, element.kind, description, `symbol-icon ${icon}`, - element.range, element.selectionRange, [], this.editorService, this + label, element.kind, description, `symbol-icon ${icon}`, element.tags && element.tags.indexOf(SymbolTag.Deprecated) >= 0, + element.range, element.selectionRange, this.editorService, this )); } @@ -504,7 +452,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { } if (model && types.isFunction((model).getLanguageIdentifier)) { - const entries = await asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)); + const entries = await getDocumentSymbols(model, true, this.pendingOutlineRequest!.token); return new OutlineModel(this.toQuickOpenEntries(entries)); } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index e20289161a..5925e614eb 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -7,8 +7,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; -import { isWeb, OperatingSystem } from 'vs/base/common/platform'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { OperatingSystem } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -49,28 +48,24 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as { - if (remoteEnvironment) { - this.labelService.registerFormatter({ - scheme: Schemas.vscodeRemote, - authority: this.environmentService.configuration.remoteAuthority, - formatting: { - label: '${path}', - separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', - tildify: remoteEnvironment.os !== OperatingSystem.Windows, - normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows - } - }); - } - }); - } + this.remoteAgentService.getEnvironment().then(remoteEnvironment => { + if (remoteEnvironment) { + this.labelService.registerFormatter({ + scheme: Schemas.vscodeRemote, + formatting: { + label: '${path}', + separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', + tildify: remoteEnvironment.os !== OperatingSystem.Windows, + normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows + } + }); + } + }); } } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index c88ef93653..fc0384d346 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -309,7 +309,8 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IProgressService progressService: IProgressService, @IDialogService dialogService: IDialogService, - @ICommandService commandService: ICommandService + @ICommandService commandService: ICommandService, + @IContextKeyService contextKeyService: IContextKeyService ) { const connection = remoteAgentService.getConnection(); if (connection) { @@ -318,6 +319,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { let lastLocation: ProgressLocation | null = null; let currentTimer: ReconnectionTimer | null = null; let reconnectWaitEvent: ReconnectionWaitEvent | null = null; + let disposableListener: IDisposable | null = null; function showProgress(location: ProgressLocation, buttons?: string[]) { if (currentProgressPromiseResolve) { @@ -331,7 +333,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { // Show dialog progressService!.withProgress( { location: ProgressLocation.Dialog, buttons }, - (progress) => { progressReporter = new ProgressReporter(progress); return promise; }, + (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, (choice?) => { // Handle choice from dialog if (choice === 0 && buttons && reconnectWaitEvent) { @@ -351,7 +353,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { // Handle choice from notification if (choice === 0 && buttons && reconnectWaitEvent) { reconnectWaitEvent.skipWait(); - progressReporter!.report(); } else { hideProgress(); } @@ -365,7 +366,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } currentProgressPromiseResolve = null; - progressReporter = null; } connection.onDidStateChange((e) => { @@ -373,9 +373,15 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { currentTimer.dispose(); currentTimer = null; } + + if (disposableListener) { + disposableListener.dispose(); + disposableListener = null; + } switch (e.type) { case PersistentConnectionEventType.ConnectionLost: if (!currentProgressPromiseResolve) { + progressReporter = new ProgressReporter(null); showProgress(ProgressLocation.Dialog, [nls.localize('reconnectNow', "Reconnect Now")]); } @@ -391,9 +397,24 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { hideProgress(); showProgress(lastLocation || ProgressLocation.Notification); progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); + + // Register to listen for quick input is opened + disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => { + const reconnectInteraction = new Set(['inQuickOpen']); + if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) { + // Need to move from dialog if being shown and user needs to type in a prompt + if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { + hideProgress(); + showProgress(ProgressLocation.Notification); + progressReporter.report(); + } + } + }); + break; case PersistentConnectionEventType.ReconnectionPermanentFailure: hideProgress(); + progressReporter = null; dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(choice => { // Reload the window @@ -404,6 +425,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { break; case PersistentConnectionEventType.ConnectionGain: hideProgress(); + progressReporter = null; break; } }); diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 21bf12915f..7decb4fb22 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -8,13 +8,13 @@ import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ThrottledDelayer } from 'vs/base/common/async'; import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen'; -import { QuickOpenModel, QuickOpenEntry, compareEntries } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { QuickOpenModel, QuickOpenEntry, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import * as filters from 'vs/base/common/filters'; import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { symbolKindToCssClass } from 'vs/editor/common/modes'; +import { symbolKindToCssClass, SymbolTag } from 'vs/editor/common/modes'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -25,9 +25,12 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; class SymbolEntry extends EditorQuickOpenEntry { - private bearingResolve: Promise | undefined; + + private bearingResolve?: Promise; + private score?: filters.FuzzyScore; constructor( private bearing: IWorkspaceSymbol, @@ -40,6 +43,14 @@ class SymbolEntry extends EditorQuickOpenEntry { super(editorService); } + setScore(score: filters.FuzzyScore | undefined) { + this.score = score; + } + + getHighlights(): [IHighlight[] /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { + return [this.isDeprecated() ? [] : filters.createMatches(this.score), undefined, undefined]; + } + getLabel(): string { return this.bearing.name; } @@ -65,10 +76,18 @@ class SymbolEntry extends EditorQuickOpenEntry { return symbolKindToCssClass(this.bearing.kind); } + getLabelOptions(): IIconLabelValueOptions | undefined { + return this.isDeprecated() ? { extraClasses: ['deprecated'] } : undefined; + } + getResource(): URI { return this.bearing.location.uri; } + private isDeprecated(): boolean { + return this.bearing.tags ? this.bearing.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; + } + run(mode: Mode, context: IEntryRunContext): boolean { // resolve this type bearing if neccessary @@ -111,18 +130,24 @@ class SymbolEntry extends EditorQuickOpenEntry { return input; } - static compare(elementA: SymbolEntry, elementB: SymbolEntry, searchValue: string): number { - - // Sort by Type if name is identical - const elementAName = elementA.getLabel().toLowerCase(); - const elementBName = elementB.getLabel().toLowerCase(); - if (elementAName === elementBName) { - let elementAType = symbolKindToCssClass(elementA.bearing.kind); - let elementBType = symbolKindToCssClass(elementB.bearing.kind); - return elementAType.localeCompare(elementBType); + static compare(a: SymbolEntry, b: SymbolEntry, searchValue: string): number { + // order: score, name, kind + if (a.score && b.score) { + if (a.score[0] > b.score[0]) { + return -1; + } else if (a.score[0] < b.score[0]) { + return 1; + } } - - return compareEntries(elementA, elementB, searchValue); + const aName = a.getLabel().toLowerCase(); + const bName = b.getLabel().toLowerCase(); + let res = aName.localeCompare(bName); + if (res !== 0) { + return res; + } + let aKind = symbolKindToCssClass(a.bearing.kind); + let bKind = symbolKindToCssClass(b.bearing.kind); + return aKind.localeCompare(bKind); } } @@ -198,6 +223,9 @@ export class OpenSymbolHandler extends QuickOpenHandler { private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void { + const pattern = strings.stripWildcards(searchValue); + const patternLow = pattern.toLowerCase(); + // Convert to Entries for (let element of types) { if (this.options.skipLocalSymbols && !!element.containerName) { @@ -205,7 +233,11 @@ export class OpenSymbolHandler extends QuickOpenHandler { } const entry = this.instantiationService.createInstance(SymbolEntry, element, provider); - entry.setHighlights(filters.matchesFuzzy2(searchValue, entry.getLabel()) || []); + entry.setScore(filters.fuzzyScore( + pattern, patternLow, 0, + entry.getLabel(), entry.getLabel().toLowerCase(), 0, + true + )); bucket.push(entry); } } diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts index 8238a18210..18dd9ec2cc 100644 --- a/src/vs/workbench/contrib/search/common/search.ts +++ b/src/vs/workbench/contrib/search/common/search.ts @@ -6,7 +6,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { SymbolKind, Location, ProviderResult } from 'vs/editor/common/modes'; +import { SymbolKind, Location, ProviderResult, SymbolTag } from 'vs/editor/common/modes'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; @@ -19,6 +19,7 @@ export interface IWorkspaceSymbol { name: string; containerName?: string; kind: SymbolKind; + tags?: SymbolTag[]; location: Location; } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index e09f9f5397..846da0cf03 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -340,9 +340,7 @@ export class TerminalTaskSystem implements ITaskSystem { private async executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise { let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { - // tslint:disable-next-line: no-for-in-array - for (let index in task.configurationProperties.dependsOn) { - const dependency = task.configurationProperties.dependsOn[index]; + for (const dependency of task.configurationProperties.dependsOn) { let dependencyTask = resolver.resolve(dependency.workspaceFolder, dependency.task!); if (dependencyTask) { let key = dependencyTask.getMapKey(); diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index e390f5af8c..a3ced76337 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -216,11 +216,11 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil if (fullPath === undefined) { throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.'); } + fullPath = normalize(fullPath); fullPath = fullPath.replace(/\\/g, '/'); if (fullPath[0] !== '/') { fullPath = '/' + fullPath; } - fullPath = normalize(fullPath); if (matcher.uriProvider !== undefined) { return matcher.uriProvider(fullPath); } else { diff --git a/src/vs/workbench/contrib/tasks/common/taskTemplates.ts b/src/vs/workbench/contrib/tasks/common/taskTemplates.ts index fffbe0c6e6..62e3bcdc7b 100644 --- a/src/vs/workbench/contrib/tasks/common/taskTemplates.ts +++ b/src/vs/workbench/contrib/tasks/common/taskTemplates.ts @@ -27,9 +27,10 @@ const dotnetBuild: TaskEntry = { '\t"tasks": [', '\t\t{', '\t\t\t"label": "build",', - '\t\t\t"command": "dotnet build",', + '\t\t\t"command": "dotnet",', '\t\t\t"type": "shell",', '\t\t\t"args": [', + '\t\t\t\t"build",', '\t\t\t\t// Ask dotnet build to generate full paths for file names.', '\t\t\t\t"/property:GenerateFullPaths=true",', '\t\t\t\t// Do not generate summary otherwise it leads to duplicate errors in Problems panel', diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 35f3b1ca30..1b6a7f1da6 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -985,15 +985,14 @@ export namespace KeyedTaskIdentifier { function sortedStringify(literal: any): string { const keys = Object.keys(literal).sort(); let result: string = ''; - // tslint:disable-next-line: no-for-in-array - for (let position in keys) { - let stringified = literal[keys[position]]; + for (const key of keys) { + let stringified = literal[key]; if (stringified instanceof Object) { stringified = sortedStringify(stringified); } else if (typeof stringified === 'string') { stringified = stringified.replace(/,/g, ',,'); } - result += keys[position] + ',' + stringified + ','; + result += key + ',' + stringified + ','; } return result; } diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 6f8dc3b908..056a231b42 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -52,7 +52,10 @@ Registry.as(ActionExtensions.WorkbenchActions).registe const DEAFULT_TRUSTED_DOMAINS = [ 'https://code.visualstudio.com', - 'https://go.microsoft.com' + 'https://go.microsoft.com', + 'https://github.com', + 'https://marketplace.visualstudio.com', + 'https://vscode-auth.github.com' ]; const configureTrustedDomainsHandler = async ( diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 4e8156cd7a..10fab9a16a 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -194,7 +194,7 @@ class CodeRendererMain extends Disposable { const signService = new SignService(); serviceCollection.set(ISignService, signService); - const remoteAgentService = this._register(new RemoteAgentService(this.environmentService.configuration, this.environmentService, remoteAuthorityResolverService, signService)); + const remoteAgentService = this._register(new RemoteAgentService(this.environmentService.configuration, this.environmentService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); // Files diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 2a068ce7fa..fcc2f287a1 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -55,7 +55,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR if (activeEditor instanceof DiffEditorInput) { activeEditor = activeEditor.modifiedInput; } - const fileResource = toResource(activeEditor, { filterByScheme: Schemas.file }); + const fileResource = toResource(activeEditor, { filterByScheme: [Schemas.file, Schemas.userData] }); if (!fileResource) { return undefined; } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 80a0df64f3..d3965333a3 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -79,7 +79,8 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH return { host: authority.host, port: authority.port }; } }, - signService: this._signService + signService: this._signService, + logService: this._logService }; return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => { diff --git a/src/vs/workbench/services/extensions/common/staticExtensions.ts b/src/vs/workbench/services/extensions/common/staticExtensions.ts index 64eda6d064..344f0a5ae7 100644 --- a/src/vs/workbench/services/extensions/common/staticExtensions.ts +++ b/src/vs/workbench/services/extensions/common/staticExtensions.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionDescription, IExtensionManifest, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { UriComponents, URI } from 'vs/base/common/uri'; +import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export const IStaticExtensionsService = createDecorator('IStaticExtensionsService'); @@ -20,7 +22,9 @@ export class StaticExtensionsService implements IStaticExtensionsService { private readonly _descriptions: IExtensionDescription[] = []; - constructor(staticExtensions: { packageJSON: IExtensionManifest, extensionLocation: UriComponents }[]) { + constructor(@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) { + const staticExtensions = environmentService.options && Array.isArray(environmentService.options.staticExtensions) ? environmentService.options.staticExtensions : []; + this._descriptions = staticExtensions.map(data => { identifier: new ExtensionIdentifier(`${data.packageJSON.publisher}.${data.packageJSON.name}`), extensionLocation: URI.revive(data.extensionLocation), @@ -32,3 +36,5 @@ export class StaticExtensionsService implements IStaticExtensionsService { return this._descriptions; } } + +registerSingleton(IStaticExtensionsService, StaticExtensionsService, true); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 995ceca43d..4c9fa62687 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -7,9 +7,9 @@ import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; import * as minimist from 'vscode-minimist'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { PersistentProtocol, ProtocolConstants, createBufferedEvent } from 'vs/base/parts/ipc/common/ipc.net'; +import { PersistentProtocol, ProtocolConstants, BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net'; import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import product from 'vs/platform/product/node/product'; import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; @@ -164,8 +164,8 @@ async function createExtHostProtocol(): Promise { return new class implements IMessagePassingProtocol { - private readonly _onMessage = new Emitter(); - readonly onMessage: Event = createBufferedEvent(this._onMessage.event); + private readonly _onMessage = new BufferedEmitter(); + readonly onMessage: Event = this._onMessage.event; private _terminating: boolean; diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index d767e85362..9468017f36 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -12,15 +12,31 @@ import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/exten import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import 'vs/workbench/services/extensions/worker/extHost.services'; -// worker-self +//#region --- Define, capture, and override some globals +//todo@joh do not allow extensions to call postMessage and other globals... + declare namespace self { - function close(): void; + let close: any; + let postMessage: any; + let addEventLister: any; + let indexedDB: any; + let caches: any; } -// do not allow extensions to call terminate const nativeClose = self.close.bind(self); -self.close = () => console.trace('An extension called terminate and this was prevented'); -let onTerminate = nativeClose; +self.close = () => console.trace(`'close' has been blocked`); + +const nativePostMessage = postMessage.bind(self); +self.postMessage = () => console.trace(`'postMessage' has been blocked`); + +const nativeAddEventLister = addEventListener.bind(self); +self.addEventLister = () => console.trace(`'addEventListener' has been blocked`); + +// readonly, cannot redefine... +// self.indexedDB = undefined; +// self.caches = undefined; + +//#endregion --- const hostUtil = new class implements IHostUtils { _serviceBrand: any; @@ -35,7 +51,6 @@ const hostUtil = new class implements IHostUtils { } }; -//todo@joh do not allow extensions to call postMessage and other globals... class ExtensionWorker { @@ -47,7 +62,8 @@ class ExtensionWorker { let emitter = new Emitter(); let terminating = false; - onmessage = event => { + + nativeAddEventLister('message', event => { const { data } = event; if (!(data instanceof ArrayBuffer)) { console.warn('UNKNOWN data received', data); @@ -64,14 +80,14 @@ class ExtensionWorker { // emit non-terminate messages to the outside emitter.fire(msg); - }; + }); this.protocol = { onMessage: emitter.event, send: vsbuf => { if (!terminating) { const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); - postMessage(data, [data]); + nativePostMessage(data, [data]); } } }; @@ -94,6 +110,8 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { const uri1 = TestWorkspace.folders[0].uri.with({ path: TestWorkspace.folders[0].uri.path.concat('/a/b/c/d') }); assert.equal(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d'); assert.equal(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d'); + assert.equal(labelService.getUriBasenameLabel(uri1), 'd'); const uri2 = URI.file('c:\\1/2/3'); assert.equal(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3'); + assert.equal(labelService.getUriBasenameLabel(uri2), '3'); }); test('custom scheme', function () { @@ -51,6 +53,23 @@ suite('URI Label', () => { const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END'); + assert.equal(labelService.getUriBasenameLabel(uri1), 'END'); + }); + + test('separator', function () { + labelService.registerFormatter({ + scheme: 'vscode', + formatting: { + label: 'LABEL\\${path}\\${authority}\\END', + separator: '\\', + tildify: true, + normalizeDriveLetter: true + } + }); + + const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); + assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL\\\\1\\2\\3\\4\\5\\microsoft.com\\END'); + assert.equal(labelService.getUriBasenameLabel(uri1), 'END'); }); test('custom authority', function () { @@ -65,6 +84,7 @@ suite('URI Label', () => { const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END'); + assert.equal(labelService.getUriBasenameLabel(uri1), 'END'); }); test('mulitple authority', function () { @@ -96,6 +116,7 @@ suite('URI Label', () => { // Make sure the most specific authority is picked const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'second'); + assert.equal(labelService.getUriBasenameLabel(uri1), 'second'); }); test('custom query', function () { diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index b5345d054f..43c5388ca2 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -11,6 +11,7 @@ import { IProductService } from 'vs/platform/product/common/product'; import { IWebSocketFactory, BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; +import { ILogService } from 'vs/platform/log/common/log'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { @@ -23,12 +24,13 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @ISignService signService: ISignService + @ISignService signService: ISignService, + @ILogService logService: ILogService ) { super(environmentService); this.socketFactory = new BrowserSocketFactory(webSocketFactory); - this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, this.socketFactory, remoteAuthorityResolverService, signService)); + this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, this.socketFactory, remoteAuthorityResolverService, signService, logService)); } getConnection(): IRemoteAgentConnection | null { diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index f1c1678676..b524881df1 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -20,6 +20,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { Emitter } from 'vs/base/common/event'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { ILogService } from 'vs/platform/log/common/log'; export abstract class AbstractRemoteAgentService extends Disposable { @@ -86,7 +87,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon private readonly _commit: string | undefined, private readonly _socketFactory: ISocketFactory, private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, - private readonly _signService: ISignService + private readonly _signService: ISignService, + private readonly _logService: ILogService ) { super(); this.remoteAuthority = remoteAuthority; @@ -124,7 +126,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon return { host: authority.host, port: authority.port }; } }, - signService: this._signService + signService: this._signService, + logService: this._logService }; const connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`)); this._register(connection.onDidStateChange(e => this._onDidStateChange.fire(e))); diff --git a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index b72a21d935..8ede3c2029 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -12,6 +12,7 @@ import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; +import { ILogService } from 'vs/platform/log/common/log'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { @@ -22,12 +23,13 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR constructor({ remoteAuthority }: IWindowConfiguration, @IEnvironmentService environmentService: IEnvironmentService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @ISignService signService: ISignService + @ISignService signService: ISignService, + @ILogService logService: ILogService ) { super(environmentService); this.socketFactory = nodeSocketFactory; if (remoteAuthority) { - this._connection = this._register(new RemoteAgentConnection(remoteAuthority, product.commit, nodeSocketFactory, remoteAuthorityResolverService, signService)); + this._connection = this._register(new RemoteAgentConnection(remoteAuthority, product.commit, nodeSocketFactory, remoteAuthorityResolverService, signService, logService)); } } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 093c7d4f06..574396f14e 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -15,6 +15,7 @@ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort); @@ -90,7 +91,8 @@ export class TunnelService implements ITunnelService { public constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @ISignService private readonly signService: ISignService + @ISignService private readonly signService: ISignService, + @ILogService private readonly logService: ILogService ) { } @@ -109,7 +111,8 @@ export class TunnelService implements ITunnelService { return { host: authority.host, port: authority.port }; } }, - signService: this.signService + signService: this.signService, + logService: this.logService }; return createRemoteTunnel(options, remotePort); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index dd68f51c13..db8a8aae02 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { ITextFileService, IResourceEncodings, IResourceEncoding } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -26,6 +26,10 @@ export class BrowserTextFileService extends TextFileService { } private doBeforeShutdownSync(): boolean { + if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { + return true; // files are pending to be saved: veto + } + const dirtyResources = this.getDirty(); if (!dirtyResources.length) { return false; // no dirty: no veto diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 04ce61edf4..844da9a88e 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -593,6 +593,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Create new save timer and store it for disposal as needed const handle = setTimeout(() => { + // Clear the timeout now that we are running + this.autoSaveDisposable.clear(); + // Only trigger save if the version id has not changed meanwhile if (versionId === this.versionId) { this.doSave(versionId, { reason: SaveReason.AUTO }); // Very important here to not return the promise because if the timeout promise is canceled it will bubble up the error otherwise - do not change @@ -943,6 +946,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.inOrphanMode; case ModelState.PENDING_SAVE: return this.saveSequentializer.hasPendingSave(); + case ModelState.PENDING_AUTO_SAVE: + return !!this.autoSaveDisposable.value; case ModelState.SAVED: return !this.dirty; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index f5a61d77de..06a787db61 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -248,10 +248,15 @@ export const enum ModelState { DIRTY, /** - * A model is transitioning from dirty to saved. + * A model is currently being saved but this operation has not completed yet. */ PENDING_SAVE, + /** + * A model is marked for being saved after a specific timeout. + */ + PENDING_AUTO_SAVE, + /** * A model is in conflict mode when changes cannot be saved because the * underlying file has changed. Models in conflict mode are always dirty. diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 66e12f72a7..35c74048a4 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -52,6 +52,7 @@ suite('Files - TextFileEditorModel', () => { model.textEditorModel!.setValue('bar'); assert.ok(getLastModifiedTime(model) <= Date.now()); + assert.ok(model.hasState(ModelState.DIRTY)); let savedEvent = false; model.onDidStateChange(e => { @@ -60,9 +61,13 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.save(); + const pendingSave = model.save(); + assert.ok(model.hasState(ModelState.PENDING_SAVE)); + + await pendingSave; assert.ok(model.getLastSaveAttemptTime() <= Date.now()); + assert.ok(model.hasState(ModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 8963f04728..b44e4fcbd5 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -75,6 +75,7 @@ import 'vs/workbench/services/themes/browser/workbenchThemeService'; import 'vs/workbench/services/label/common/labelService'; import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; import 'vs/workbench/services/notification/common/notificationService'; +import 'vs/workbench/services/extensions/common/staticExtensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index c991548a05..c2fa09636f 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -71,7 +71,6 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; -import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; registerSingleton(IClipboardService, ClipboardService, true); registerSingleton(IRequestService, RequestService, true); @@ -83,7 +82,6 @@ registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); -registerSingleton(IStaticExtensionsService, class extends StaticExtensionsService { constructor() { super([]); } }); //#endregion diff --git a/src/vs/workbench/workbench.web.main.css b/src/vs/workbench/workbench.web.api.css similarity index 94% rename from src/vs/workbench/workbench.web.main.css rename to src/vs/workbench/workbench.web.api.css index 04a7af7a08..2dd7922137 100644 --- a/src/vs/workbench/workbench.web.main.css +++ b/src/vs/workbench/workbench.web.api.css @@ -4,6 +4,3 @@ *--------------------------------------------------------------------------------------------*/ /* NOTE: THIS FILE WILL BE OVERWRITTEN DURING BUILD TIME, DO NOT EDIT */ - -div.monaco.main.css { -} diff --git a/src/vs/workbench/workbench.web.main.nls.js b/src/vs/workbench/workbench.web.api.nls.js similarity index 96% rename from src/vs/workbench/workbench.web.main.nls.js rename to src/vs/workbench/workbench.web.api.nls.js index 28f20a5e93..cd3f020911 100644 --- a/src/vs/workbench/workbench.web.main.nls.js +++ b/src/vs/workbench/workbench.web.api.nls.js @@ -4,5 +4,3 @@ *--------------------------------------------------------------------------------------------*/ // NOTE: THIS FILE WILL BE OVERWRITTEN DURING BUILD TIME, DO NOT EDIT - -define([], {});