diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 66b7fa005c..e9033d16cf 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -188,6 +188,9 @@ export function activate(context: ExtensionContext) { // handle content request client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { const uri = Uri.parse(uriPath); + if (uri.scheme === 'untitled') { + return Promise.reject(new Error(localize('untitled.schema', 'Unable to load {0}', uri.toString()))); + } if (uri.scheme !== 'http' && uri.scheme !== 'https') { return workspace.openTextDocument(uri).then(doc => { schemaDocuments[uri.toString()] = true; diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 72ca23bf07..be1baa4f1f 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as strings from 'vs/base/common/strings'; + /** * Return a hash value for an object. */ @@ -70,3 +72,235 @@ export class Hasher { return this._value; } } + +const enum SHA1Constant { + BLOCK_SIZE = 64, // 512 / 8 + UNICODE_REPLACEMENT = 0xFFFD, +} + +function leftRotate(value: number, bits: number, totalBits: number = 32): number { + // delta + bits = totalBits + const delta = totalBits - bits; + + // All ones, expect `delta` zeros aligned to the right + const mask = ~((1 << delta) - 1); + + // Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits) + return ((value << bits) | ((mask & value) >>> delta)) >>> 0; +} + +function fill(dest: Uint8Array, index: number = 0, count: number = dest.byteLength, value: number = 0): void { + for (let i = 0; i < count; i++) { + dest[index + i] = value; + } +} + +function leftPad(value: string, length: number, char: string = '0'): string { + while (value.length < length) { + value = char + value; + } + return value; +} + +function toHexString(value: number, bitsize: number = 32): string { + return leftPad((value >>> 0).toString(16), bitsize / 4); +} + +/** + * A SHA1 implementation that works with strings and does not allocate. + */ +export class StringSHA1 { + private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320 + + private _h0 = 0x67452301; + private _h1 = 0xEFCDAB89; + private _h2 = 0x98BADCFE; + private _h3 = 0x10325476; + private _h4 = 0xC3D2E1F0; + + private readonly _buff: Uint8Array; + private readonly _buffDV: DataView; + private _buffLen: number; + private _totalLen: number; + private _leftoverHighSurrogate: number; + private _finished: boolean; + + constructor() { + this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */); + this._buffDV = new DataView(this._buff.buffer); + this._buffLen = 0; + this._totalLen = 0; + this._leftoverHighSurrogate = 0; + this._finished = false; + } + + public update(str: string): void { + const strLen = str.length; + if (strLen === 0) { + return; + } + + const buff = this._buff; + let buffLen = this._buffLen; + let leftoverHighSurrogate = this._leftoverHighSurrogate; + let charCode: number; + let offset: number; + + if (leftoverHighSurrogate !== 0) { + charCode = leftoverHighSurrogate; + offset = -1; + leftoverHighSurrogate = 0; + } else { + charCode = str.charCodeAt(0); + offset = 0; + } + + while (true) { + let codePoint = charCode; + if (strings.isHighSurrogate(charCode)) { + if (offset + 1 < strLen) { + const nextCharCode = str.charCodeAt(offset + 1); + if (strings.isLowSurrogate(nextCharCode)) { + offset++; + codePoint = strings.computeCodePoint(charCode, nextCharCode); + } else { + // illegal => unicode replacement character + codePoint = SHA1Constant.UNICODE_REPLACEMENT; + } + } else { + // last character is a surrogate pair + leftoverHighSurrogate = charCode; + break; + } + } else if (strings.isLowSurrogate(charCode)) { + // illegal => unicode replacement character + codePoint = SHA1Constant.UNICODE_REPLACEMENT; + } + + buffLen = this._push(buff, buffLen, codePoint); + offset++; + if (offset < strLen) { + charCode = str.charCodeAt(offset); + } else { + break; + } + } + + this._buffLen = buffLen; + this._leftoverHighSurrogate = leftoverHighSurrogate; + } + + private _push(buff: Uint8Array, buffLen: number, codePoint: number): number { + if (codePoint < 0x0080) { + buff[buffLen++] = codePoint; + } else if (codePoint < 0x0800) { + buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } else if (codePoint < 0x10000) { + buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } else { + buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } + + if (buffLen >= SHA1Constant.BLOCK_SIZE) { + this._step(); + buffLen -= SHA1Constant.BLOCK_SIZE; + this._totalLen += SHA1Constant.BLOCK_SIZE; + // take last 3 in case of UTF8 overflow + buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0]; + buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1]; + buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2]; + } + + return buffLen; + } + + public digest(): string { + if (!this._finished) { + this._finished = true; + if (this._leftoverHighSurrogate) { + // illegal => unicode replacement character + this._leftoverHighSurrogate = 0; + this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT); + } + this._totalLen += this._buffLen; + this._wrapUp(); + } + + return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4); + } + + private _wrapUp(): void { + this._buff[this._buffLen++] = 0x80; + fill(this._buff, this._buffLen); + + if (this._buffLen > 56) { + this._step(); + fill(this._buff); + } + + // this will fit because the mantissa can cover up to 52 bits + const ml = 8 * this._totalLen; + + this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false); + this._buffDV.setUint32(60, ml % 4294967296, false); + + this._step(); + } + + private _step(): void { + const bigBlock32 = StringSHA1._bigBlock32; + const data = this._buffDV; + + for (let j = 0; j < 64 /* 16*4 */; j += 4) { + bigBlock32.setUint32(j, data.getUint32(j, false), false); + } + + for (let j = 64; j < 320 /* 80*4 */; j += 4) { + bigBlock32.setUint32(j, leftRotate((bigBlock32.getUint32(j - 12, false) ^ bigBlock32.getUint32(j - 32, false) ^ bigBlock32.getUint32(j - 56, false) ^ bigBlock32.getUint32(j - 64, false)), 1), false); + } + + let a = this._h0; + let b = this._h1; + let c = this._h2; + let d = this._h3; + let e = this._h4; + + let f: number, k: number; + let temp: number; + + for (let j = 0; j < 80; j++) { + if (j < 20) { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } else if (j < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (j < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff; + e = d; + d = c; + c = leftRotate(b, 30); + b = a; + a = temp; + } + + this._h0 = (this._h0 + a) & 0xffffffff; + this._h1 = (this._h1 + b) & 0xffffffff; + this._h2 = (this._h2 + c) & 0xffffffff; + this._h3 = (this._h3 + d) & 0xffffffff; + this._h4 = (this._h4 + e) & 0xffffffff; + } +} diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 7fd8352939..20b1794e42 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -428,29 +428,27 @@ export function commonSuffixLength(a: string, b: string): number { return len; } -// --- unicode -// http://en.wikipedia.org/wiki/Surrogate_pair -// Returns the code point starting at a specified index in a string -// Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character -// Code points U+10000 to U+10FFFF are represented on two consecutive characters -//export function getUnicodePoint(str:string, index:number, len:number):number { -// const chrCode = str.charCodeAt(index); -// if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) { -// const nextChrCode = str.charCodeAt(index + 1); -// if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) { -// return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000; -// } -// } -// return chrCode; -//} +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ export function isHighSurrogate(charCode: number): boolean { return (0xD800 <= charCode && charCode <= 0xDBFF); } +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ export function isLowSurrogate(charCode: number): boolean { return (0xDC00 <= charCode && charCode <= 0xDFFF); } +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ +export function computeCodePoint(highSurrogate: number, lowSurrogate: number): number { + return ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00) + 0x10000; +} + /** * get the code point that begins at offset `offset` */ @@ -459,7 +457,7 @@ export function getNextCodePoint(str: string, len: number, offset: number): numb if (isHighSurrogate(charCode) && offset + 1 < len) { const nextCharCode = str.charCodeAt(offset + 1); if (isLowSurrogate(nextCharCode)) { - return ((charCode - 0xD800) << 10) + (nextCharCode - 0xDC00) + 0x10000; + return computeCodePoint(charCode, nextCharCode); } } return charCode; @@ -473,7 +471,7 @@ function getPrevCodePoint(str: string, offset: number): number { if (isLowSurrogate(charCode) && offset > 1) { const prevCharCode = str.charCodeAt(offset - 2); if (isHighSurrogate(prevCharCode)) { - return ((prevCharCode - 0xD800) << 10) + (charCode - 0xDC00) + 0x10000; + return computeCodePoint(prevCharCode, charCode); } } return charCode; diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index d2fe022be2..4ea16d8eea 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -239,6 +239,7 @@ margin-right: 8px; } +.quick-input-list .quick-input-list-entry.always-visible-actions .quick-input-list-entry-action-bar, .quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar, .quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar { display: flex; diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index 4f8fac5424..69f3f9297d 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -185,6 +185,12 @@ class ListElementRenderer implements IListRenderer { diff --git a/src/vs/base/test/common/hash.test.ts b/src/vs/base/test/common/hash.test.ts index 4a1a6b46a5..3eb42d86d6 100644 --- a/src/vs/base/test/common/hash.test.ts +++ b/src/vs/base/test/common/hash.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { hash } from 'vs/base/common/hash'; +import { hash, StringSHA1 } from 'vs/base/common/hash'; suite('Hash', () => { test('string', () => { @@ -53,4 +53,28 @@ suite('Hash', () => { assert.notEqual(a, b); }); + function checkSHA1(strings: string[], expected: string) { + const hash = new StringSHA1(); + for (const str of strings) { + hash.update(str); + } + const actual = hash.digest(); + assert.equal(actual, expected); + } + + test('sha1-1', () => { + checkSHA1(['\udd56'], '9bdb77276c1852e1fb067820472812fcf6084024'); + }); + + test('sha1-2', () => { + checkSHA1(['\udb52'], '9bdb77276c1852e1fb067820472812fcf6084024'); + }); + + test('sha1-3', () => { + checkSHA1(['\uda02ꑍ'], '9b483a471f22fe7e09d83f221871a987244bbd3f'); + }); + + test('sha1-4', () => { + checkSHA1(['hello'], 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'); + }); }); diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 5aee084792..e9bd6a3675 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -616,7 +616,15 @@ class RenderedViewLine implements IRenderedViewLine { if (!r || r.length === 0) { return -1; } - return r[0].left; + const result = r[0].left; + if (this.input.isBasicASCII) { + const charOffset = this._characterMapping.getAbsoluteOffsets(); + const expectedResult = Math.round(this.input.spaceWidth * charOffset[column - 1]); + if (Math.abs(expectedResult - result) <= 1) { + return expectedResult; + } + } + return result; } private _readRawVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { diff --git a/src/vs/editor/contrib/caretOperations/caretOperations.ts b/src/vs/editor/contrib/caretOperations/caretOperations.ts index ac2261f3ce..6b9a6b3223 100644 --- a/src/vs/editor/contrib/caretOperations/caretOperations.ts +++ b/src/vs/editor/contrib/caretOperations/caretOperations.ts @@ -42,8 +42,8 @@ class MoveCaretLeftAction extends MoveCaretAction { constructor() { super(true, { id: 'editor.action.moveCarretLeftAction', - label: nls.localize('caret.moveLeft', "Move Caret Left"), - alias: 'Move Caret Left', + label: nls.localize('caret.moveLeft', "Move Selected Text Left"), + alias: 'Move Selected Text Left', precondition: EditorContextKeys.writable }); } @@ -53,8 +53,8 @@ class MoveCaretRightAction extends MoveCaretAction { constructor() { super(false, { id: 'editor.action.moveCarretRightAction', - label: nls.localize('caret.moveRight', "Move Caret Right"), - alias: 'Move Caret Right', + label: nls.localize('caret.moveRight', "Move Selected Text Right"), + alias: 'Move Selected Text Right', precondition: EditorContextKeys.writable }); } diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 44c8b462ac..8affad105b 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -330,7 +330,11 @@ class ShowAccessibilityHelpAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F1, - weight: KeybindingWeight.EditorContrib + weight: KeybindingWeight.EditorContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, + secondary: [KeyMod.Alt | KeyCode.F1] + } } }); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 98a84824a5..6dcefc8ff7 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -378,7 +378,7 @@ export interface IAction2Options extends ICommandAction { /** * One or many menu items. */ - menu?: OneOrN<{ id: MenuId } & Omit & { command?: Partial> }>; + menu?: OneOrN<{ id: MenuId } & Omit>; /** * One keybinding. @@ -401,7 +401,7 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { const disposables = new DisposableStore(); const action = new ctor(); - const { f1, menu: menus, keybinding, description, ...command } = action.desc; + const { f1, menu, keybinding, description, ...command } = action.desc; // command disposables.add(CommandsRegistry.registerCommand({ @@ -411,14 +411,12 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { })); // menu - if (Array.isArray(menus)) { - for (let item of menus) { - const { command: commandOverrides, ...menu } = item; - disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command, ...commandOverrides }, ...menu })); + if (Array.isArray(menu)) { + for (let item of menu) { + disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command }, ...item })); } - } else if (menus) { - const { command: commandOverrides, ...menu } = menus; - disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command, ...commandOverrides }, ...menu })); + } else if (menu) { + disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command }, ...menu })); } if (f1) { disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command })); diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 271f16c80d..ff3e15a756 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -45,7 +45,8 @@ export const enum ProgressLocation { Extensions = 5, Window = 10, Notification = 15, - Dialog = 20 + Dialog = 20, + View = 25 } export interface IProgressOptions { diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index 3ee5de55f2..bdfcec7073 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -6,7 +6,6 @@ import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -22,7 +21,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { + provide(picker: IQuickPick): IDisposable { const disposables = new DisposableStore(); // Open a picker with the selected value if picked @@ -33,6 +32,15 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { } })); + // Also open a picker when we detect the user typed the exact + // name of a provider (e.g. `?term` for terminals) + disposables.add(picker.onDidChangeValue(value => { + const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length)); + if (providerDescriptor && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { + this.quickInputService.quickAccess.show(providerDescriptor.prefix); + } + })); + // Fill in all providers separated by editor/global scope const { editorProviders, globalProviders } = this.getQuickAccessProviders(); picker.items = editorProviders.length === 0 || globalProviders.length === 0 ? @@ -57,7 +65,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { const globalProviders: IHelpQuickAccessPickItem[] = []; const editorProviders: IHelpQuickAccessPickItem[] = []; - for (const provider of this.registry.getQuickAccessProviders().sort((p1, p2) => p1.prefix.localeCompare(p2.prefix))) { + for (const provider of this.registry.getQuickAccessProviders().sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))) { for (const helpEntry of provider.helpEntries) { const prefix = helpEntry.prefix || provider.prefix; const label = prefix || '\u2026' /* ... */; @@ -65,8 +73,8 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { (helpEntry.needsEditor ? editorProviders : globalProviders).push({ prefix, label, - description: helpEntry.description, - ariaLabel: localize('entryAriaLabel', "{0}, picker help", label) + ariaLabel: localize('entryAriaLabel', "{0}, quick access help picker", label), + description: helpEntry.description }); } } diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 472c2879c7..abf578dcb1 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -144,6 +144,24 @@ Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); //#region Helper class for simple picker based providers +export enum TriggerAction { + + /** + * Do nothing after the button was clicked. + */ + NO_ACTION, + + /** + * Close the picker. + */ + CLOSE_PICKER, + + /** + * Update the results of the picker. + */ + REFRESH_PICKER +} + export interface IPickerQuickAccessItem extends IQuickPickItem { /** @@ -154,14 +172,15 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { /** * A method that will be executed when a button of the pick item was - * clicked on. The picker will only close if `true` is returned. + * clicked on. * * @param buttonIndex index of the button of the item that * was clicked. * - * @returns a valud indicating if the picker should close or not. + * @returns a value that indicates what should happen after the trigger + * which can be a `Promise` for long running operations. */ - trigger?(buttonIndex: number): boolean; + trigger?(buttonIndex: number): TriggerAction | Promise; } export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { @@ -192,13 +211,18 @@ export abstract class PickerQuickAccessProvider updatePickerItems())); updatePickerItems(); @@ -213,13 +237,26 @@ export abstract class PickerQuickAccessProvider { + disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { if (typeof item.trigger === 'function') { const buttonIndex = item.buttons?.indexOf(button) ?? -1; if (buttonIndex >= 0) { - const hide = item.trigger(buttonIndex); - if (hide !== false) { - picker.hide(); + const result = item.trigger(buttonIndex); + const action = (typeof result === 'number') ? result : await result; + + if (token.isCancellationRequested) { + return; + } + + switch (action) { + case TriggerAction.NO_ACTION: + break; + case TriggerAction.CLOSE_PICKER: + picker.hide(); + break; + case TriggerAction.REFRESH_PICKER: + updatePickerItems(); + break; } } } diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index a41c9b69e1..d3b3447b1f 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -433,6 +433,7 @@ function registerDefaultClassifications(): void { registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]); + registerTokenStyleDefault('property.readonly', [['variable.other.constant.property']]); } export function getTokenClassificationRegistry(): ITokenClassificationRegistry { diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index dcf132ec79..8774a02a3f 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname } from 'vs/base/common/resources'; import { CancelablePromise } from 'vs/base/common/async'; @@ -19,6 +19,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isString } from 'vs/base/common/types'; +import { uppercaseFirstLetter } from 'vs/base/common/strings'; type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -54,10 +55,10 @@ export abstract class AbstractSynchroniser extends Disposable { readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; protected readonly lastSyncResource: URI; + protected readonly syncResourceLogLabel: string; constructor( - readonly source: SyncSource, - readonly resourceKey: ResourceKey, + readonly resource: SyncResource, @IFileService protected readonly fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, @@ -68,8 +69,9 @@ export abstract class AbstractSynchroniser extends Disposable { @IConfigurationService protected readonly configurationService: IConfigurationService, ) { super(); - this.syncFolder = joinPath(environmentService.userDataSyncHome, source); - this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resourceKey}.json`); + this.syncResourceLogLabel = uppercaseFirstLetter(this.resource); + this.syncFolder = joinPath(environmentService.userDataSyncHome, resource); + this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`); } protected setStatus(status: SyncStatus): void { @@ -79,32 +81,32 @@ export abstract class AbstractSynchroniser extends Disposable { this._onDidChangStatus.fire(status); if (status === SyncStatus.HasConflicts) { // Log to telemetry when there is a sync conflict - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.resource }); } if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { // Log to telemetry when conflicts are resolved - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource }); } } } - protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } + protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resource); } async sync(ref?: string): Promise { if (!this.isEnabled()) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`); return; } if (this.status === SyncStatus.HasConflicts) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as there are conflicts.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`); return; } if (this.status === SyncStatus.Syncing) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is running already.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`); return; } - this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); + this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -114,9 +116,9 @@ export abstract class AbstractSynchroniser extends Disposable { try { status = await this.doSync(remoteUserData, lastSyncUserData); if (status === SyncStatus.HasConflicts) { - this.logService.info(`${this.source}: Detected conflicts while synchronizing ${this.source.toLowerCase()}.`); + this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`); } else if (status === SyncStatus.Idle) { - this.logService.trace(`${this.source}: Finished synchronizing ${this.source.toLowerCase()}.`); + this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`); } } finally { this.setStatus(status); @@ -126,8 +128,8 @@ export abstract class AbstractSynchroniser extends Disposable { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source }); - throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.resource }); + throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.resource); } try { const status = await this.performSync(remoteUserData, lastSyncUserData); @@ -137,7 +139,7 @@ export abstract class AbstractSynchroniser extends Disposable { switch (e.code) { case UserDataSyncErrorCode.RemotePreconditionFailed: // Rejected as there is a new remote version. Syncing again, - this.logService.info(`${this.source}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624 remoteUserData = await this.getRemoteUserData(null); return this.doSync(remoteUserData, lastSyncUserData); @@ -163,7 +165,7 @@ export abstract class AbstractSynchroniser extends Disposable { } async getLocalBackupContent(ref?: string): Promise { - return this.userDataSyncBackupStoreService.resolveContent(this.resourceKey, ref); + return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref); } async resetLocal(): Promise { @@ -225,23 +227,23 @@ export abstract class AbstractSynchroniser extends Disposable { private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise { if (isString(refOrLastSyncData)) { - const content = await this.userDataSyncStoreService.resolveContent(this.resourceKey, refOrLastSyncData); + const content = await this.userDataSyncStoreService.resolveContent(this.resource, refOrLastSyncData); return { ref: refOrLastSyncData, content }; } else { const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null; - return this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source); + return this.userDataSyncStoreService.read(this.resource, lastSyncUserData); } } protected async updateRemoteUserData(content: string, ref: string | null): Promise { const syncData: ISyncData = { version: this.version, content }; - ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source); + ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref); return { ref, syncData }; } protected async backupLocal(content: string): Promise { const syncData: ISyncData = { version: this.version, content }; - return this.userDataSyncBackupStoreService.backup(this.resourceKey, JSON.stringify(syncData)); + return this.userDataSyncBackupStoreService.backup(this.resource, JSON.stringify(syncData)); } protected abstract readonly version: number; @@ -264,8 +266,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { constructor( protected readonly file: URI, - source: SyncSource, - resourceKey: ResourceKey, + resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @@ -275,14 +276,14 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, ) { - super(source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } async stop(): Promise { this.cancel(); - this.logService.trace(`${this.source}: Stopped synchronizing ${this.source.toLowerCase()}.`); + this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`); try { await this.fileService.del(this.conflictsPreviewResource); } catch (e) { /* ignore */ } @@ -362,8 +363,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni constructor( file: URI, - source: SyncSource, - resourceKey: ResourceKey, + resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @@ -374,7 +374,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, ) { - super(file, source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(file, resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 24e61399a2..8d72b54527 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -50,7 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, 'extensions', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register( Event.debounce( Event.any( @@ -62,14 +62,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling extensions as it is disabled.`); return; } this.stop(); try { - this.logService.info('Extensions: Started pulling extensions...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling extensions...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -84,10 +84,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // No remote exists to pull else { - this.logService.info('Extensions: Remote extensions does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote extensions does not exist.`); } - this.logService.info('Extensions: Finished pulling extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling extensions.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -95,14 +95,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async push(): Promise { if (!this.isEnabled()) { - this.logService.info('Extensions: Skipped pushing extensions as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing extensions as it is disabled.`); return; } this.stop(); try { - this.logService.info('Extensions: Started pushing extensions...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing extensions...`); this.setStatus(SyncStatus.Syncing); const localExtensions = await this.getLocalExtensions(); @@ -111,7 +111,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const remoteUserData = await this.getRemoteUserData(lastSyncUserData); await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true); - this.logService.info('Extensions: Finished pushing extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing extensions.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -148,7 +148,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } accept(content: string): Promise { - throw new Error('Extensions: Conflicts should not occur'); + throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } async hasLocalData(): Promise { @@ -177,9 +177,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localExtensions = await this.getLocalExtensions(); if (remoteExtensions) { - this.logService.trace('Extensions: Merging remote extensions with local extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); } else { - this.logService.trace('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote extensions does not exist. Synchronizing extensions for the first time.`); } const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions()); @@ -196,7 +196,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const hasChanges = added.length || removed.length || updated.length || remote; if (!hasChanges) { - this.logService.info('Extensions: No changes found during synchronizing extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`); } if (added.length || removed.length || updated.length) { @@ -208,17 +208,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (remote) { // update remote - this.logService.trace('Extensions: Updating remote extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote extensions...`); const content = JSON.stringify(remote); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('Extensions: Updated remote extensions'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote extensions`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync - this.logService.trace('Extensions: Updating last synchronized extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized extensions...`); await this.updateLastSyncUserData(remoteUserData, { skippedExtensions }); - this.logService.info('Extensions: Updated last synchronized extensions'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized extensions`); } } @@ -230,9 +230,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); await Promise.all(extensionsToRemove.map(async extensionToRemove => { - this.logService.trace('Extensions: Uninstalling local extension...', extensionToRemove.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...', extensionToRemove.identifier.i`); await this.extensionManagementService.uninstall(extensionToRemove); - this.logService.info('Extensions: Uninstalled local extension.', extensionToRemove.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.', extensionToRemove.identifier.i`); removeFromSkipped.push(extensionToRemove.identifier); })); } @@ -245,13 +245,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension: Sync only enablement state if (installedExtension && installedExtension.type === ExtensionType.System) { if (e.disabled) { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.i`); await this.extensionEnablementService.disableExtension(e.identifier); - this.logService.info('Extensions: Disabled extension', e.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.i`); } else { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.i`); await this.extensionEnablementService.enableExtension(e.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.i`); } removeFromSkipped.push(e.identifier); return; @@ -261,19 +261,19 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (extension) { try { if (e.disabled) { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.id, extension.versio`); await this.extensionEnablementService.disableExtension(extension.identifier); - this.logService.info('Extensions: Disabled extension', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.id, extension.versio`); } else { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.id, extension.versio`); await this.extensionEnablementService.enableExtension(extension.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.id, extension.versio`); } // Install only if the extension does not exist if (!installedExtension || installedExtension.manifest.version !== extension.version) { - this.logService.trace('Extensions: Installing extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...', e.identifier.id, extension.versio`); await this.extensionManagementService.installFromGallery(extension); - this.logService.info('Extensions: Installed extension.', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Installed extension.', e.identifier.id, extension.versio`); removeFromSkipped.push(extension.identifier); } } catch (error) { diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index f7a5d38e4d..990487cd56 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -41,21 +41,21 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, ) { - super(SyncSource.GlobalState, 'globalState', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling ui state as it is disabled.`); return; } this.stop(); try { - this.logService.info('UI State: Started pulling ui state...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling ui state...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -69,10 +69,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs // No remote exists to pull else { - this.logService.info('UI State: Remote UI state does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote UI state does not exist.`); } - this.logService.info('UI State: Finished pulling UI state.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling UI state.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -80,14 +80,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async push(): Promise { if (!this.isEnabled()) { - this.logService.info('UI State: Skipped pushing UI State as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing UI State as it is disabled.`); return; } this.stop(); try { - this.logService.info('UI State: Started pushing UI State...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing UI State...`); this.setStatus(SyncStatus.Syncing); const localUserData = await this.getLocalGlobalState(); @@ -95,7 +95,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const remoteUserData = await this.getRemoteUserData(lastSyncUserData); await this.apply({ local: undefined, remote: localUserData, remoteUserData, localUserData, lastSyncUserData }, true); - this.logService.info('UI State: Finished pushing UI State.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -132,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } accept(content: string): Promise { - throw new Error('UI State: Conflicts should not occur'); + throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } async hasLocalData(): Promise { @@ -160,9 +160,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const localGloablState = await this.getLocalGlobalState(); if (remoteGlobalState) { - this.logService.trace('UI State: Merging remote ui state with local ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote ui state with local ui state...`); } else { - this.logService.trace('UI State: Remote ui state does not exist. Synchronizing ui state for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`); } const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState); @@ -175,30 +175,30 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const hasChanges = local || remote; if (!hasChanges) { - this.logService.info('UI State: No changes found during synchronizing ui state.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`); } if (local) { // update local - this.logService.trace('UI State: Updating local ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`); await this.backupLocal(JSON.stringify(localUserData)); await this.writeLocalGlobalState(local); - this.logService.info('UI State: Updated local ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`); } if (remote) { // update remote - this.logService.trace('UI State: Updating remote ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`); const content = JSON.stringify(remote); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('UI State: Updated remote ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync - this.logService.trace('UI State: Updating last synchronized ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`); await this.updateLastSyncUserData(remoteUserData); - this.logService.info('UI State: Updated last synchronized ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`); } } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 2040210a39..e7e6f8fe97 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; @@ -43,19 +43,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, 'keybindings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('Keybindings: Skipped pulling keybindings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling keybindings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Keybindings: Started pulling keybindings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling keybindings...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -78,10 +78,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // No remote exists to pull else { - this.logService.info('Keybindings: Remote keybindings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote keybindings does not exist.`); } - this.logService.info('Keybindings: Finished pulling keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling keybindings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -90,14 +90,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem async push(): Promise { if (!this.isEnabled()) { - this.logService.info('Keybindings: Skipped pushing keybindings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing keybindings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Keybindings: Started pushing keybindings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing keybindings...`); this.setStatus(SyncStatus.Syncing); const fileContent = await this.getLocalFileContent(); @@ -119,10 +119,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // No local exists to push else { - this.logService.info('Keybindings: Local keybindings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Local keybindings does not exist.`); } - this.logService.info('Keybindings: Finished pushing keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing keybindings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -202,7 +202,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...`); return this.performSync(remoteUserData, lastSyncUserData); } } @@ -219,21 +219,21 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (content !== null) { if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } if (hasLocalChanged) { - this.logService.trace('Keybindings: Updating local keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`); await this.backupLocal(this.toSyncContent(content, null)); await this.updateLocalFileContent(content, fileContent); - this.logService.info('Keybindings: Updated local keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`); } if (hasRemoteChanged) { - this.logService.trace('Keybindings: Updating remote keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`); const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); - this.logService.info('Keybindings: Updated remote keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`); } // Delete the preview @@ -241,14 +241,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem await this.fileService.del(this.conflictsPreviewResource); } catch (e) { /* ignore */ } } else { - this.logService.info('Keybindings: No changes found during synchronizing keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); } if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) { - this.logService.trace('Keybindings: Updating last synchronized keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`); const lastSyncContent = this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null); await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } }); - this.logService.info('Keybindings: Updated last synchronized keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`); } this.syncPreviewResultPromise = null; @@ -276,14 +276,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '[]'; if (this.hasErrors(localContent)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } if (!lastSyncContent // First time sync || lastSyncContent !== localContent // Local has forwarded || lastSyncContent !== remoteContent // Remote has forwarded ) { - this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote keybindings with local keybindings...`); const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); // Sync only if there are changes if (result.hasChanges) { @@ -297,7 +297,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // First time syncing to remote else if (fileContent) { - this.logService.trace('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote keybindings does not exist. Synchronizing keybindings for the first time.`); content = fileContent.value.toString(); hasRemoteChanged = true; } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 3a237c4f60..47f9a90a0e 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @ITelemetryService telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncSource.Settings, 'settings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { @@ -78,14 +78,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('Settings: Skipped pulling settings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling settings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Settings: Started pulling settings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling settings...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -113,10 +113,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // No remote exists to pull else { - this.logService.info('Settings: Remote settings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote settings does not exist.`); } - this.logService.info('Settings: Finished pulling settings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling settings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -124,14 +124,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement async push(): Promise { if (!this.isEnabled()) { - this.logService.info('Settings: Skipped pushing settings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing settings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Settings: Started pushing settings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing settings...`); this.setStatus(SyncStatus.Syncing); const fileContent = await this.getLocalFileContent(); @@ -159,10 +159,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // No local exists to push else { - this.logService.info('Settings: Local settings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Local settings does not exist.`); } - this.logService.info('Settings: Finished pushing settings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing settings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -268,7 +268,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. - this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize settings as there is a new local version available. Synchronizing again...`); return this.performSync(remoteUserData, lastSyncUserData, resolvedConflicts); } } @@ -288,10 +288,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.validateContent(content); if (hasLocalChanged) { - this.logService.trace('Settings: Updating local settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`); await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(content))); await this.updateLocalFileContent(content, fileContent); - this.logService.info('Settings: Updated local settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`); } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); @@ -299,9 +299,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); const ignoredSettings = await this.getIgnoredSettings(content); content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); - this.logService.trace('Settings: Updating remote settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote settings...`); remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), forcePush ? null : remoteUserData.ref); - this.logService.info('Settings: Updated remote settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote settings`); } // Delete the preview @@ -309,13 +309,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement await this.fileService.del(this.conflictsPreviewResource); } catch (e) { /* ignore */ } } else { - this.logService.info('Settings: No changes found during synchronizing settings.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { - this.logService.trace('Settings: Updating last synchronized settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized settings...`); await this.updateLastSyncUserData(remoteUserData); - this.logService.info('Settings: Updated last synchronized settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized settings`); } this.syncPreviewResultPromise = null; @@ -343,7 +343,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (remoteSettingsSyncContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; this.validateContent(localContent); - this.logService.trace('Settings: Merging remote settings with local settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`); const ignoredSettings = await this.getIgnoredSettings(); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions); content = result.localContent || result.remoteContent; @@ -355,7 +355,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // First time syncing to remote else if (fileContent) { - this.logService.trace('Settings: Remote settings does not exist. Synchronizing settings for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`); content = fileContent.value.toString(); hasRemoteChanged = true; } @@ -406,7 +406,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement private validateContent(content: string): void { if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 7ef487541b..b1e6ba1e30 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -135,11 +135,16 @@ export function getUserDataSyncStore(productService: IProductService, configurat return undefined; } -export const ALL_RESOURCE_KEYS: ResourceKey[] = ['settings', 'keybindings', 'extensions', 'globalState']; -export type ResourceKey = 'settings' | 'keybindings' | 'extensions' | 'globalState'; +export const enum SyncResource { + Settings = 'settings', + Keybindings = 'keybindings', + Extensions = 'extensions', + GlobalState = 'globalState' +} +export const ALL_SYNC_RESOURCES: SyncResource[] = [SyncResource.Settings, SyncResource.Keybindings, SyncResource.Extensions, SyncResource.GlobalState]; export interface IUserDataManifest { - latest?: Record + latest?: Record session: string; } @@ -152,21 +157,21 @@ export const IUserDataSyncStoreService = createDecorator; - write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise; + read(resource: SyncResource, oldValue: IUserData | null): Promise; + write(resource: SyncResource, content: string, ref: string | null): Promise; manifest(): Promise; clear(): Promise; - getAllRefs(key: ResourceKey): Promise; - resolveContent(key: ResourceKey, ref: string): Promise; - delete(key: ResourceKey): Promise; + getAllRefs(resource: SyncResource): Promise; + resolveContent(resource: SyncResource, ref: string): Promise; + delete(resource: SyncResource): Promise; } export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); export interface IUserDataSyncBackupStoreService { _serviceBrand: undefined; - backup(resourceKey: ResourceKey, content: string): Promise; - getAllRefs(key: ResourceKey): Promise; - resolveContent(key: ResourceKey, ref?: string): Promise; + backup(resource: SyncResource, content: string): Promise; + getAllRefs(resource: SyncResource): Promise; + resolveContent(resource: SyncResource, ref?: string): Promise; } //#endregion @@ -195,9 +200,9 @@ export enum UserDataSyncErrorCode { export class UserDataSyncError extends Error { - constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) { + constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly resource?: SyncResource) { super(message); - this.name = `${this.code} (UserDataSyncError) ${this.source}`; + this.name = `${this.code} (UserDataSyncError) ${this.resource}`; } static toUserDataSyncError(error: Error): UserDataSyncError { @@ -206,7 +211,7 @@ export class UserDataSyncError extends Error { } const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name); if (match && match[1]) { - return new UserDataSyncError(error.message, match[1], match[2]); + return new UserDataSyncError(error.message, match[1], match[2]); } return new UserDataSyncError(error.message, UserDataSyncErrorCode.Unknown); } @@ -230,13 +235,6 @@ export interface IGlobalState { storage: IStringDictionary; } -export const enum SyncSource { - Settings = 'Settings', - Keybindings = 'Keybindings', - Extensions = 'Extensions', - GlobalState = 'GlobalState' -} - export const enum SyncStatus { Uninitialized = 'uninitialized', Idle = 'idle', @@ -246,8 +244,7 @@ export const enum SyncStatus { export interface IUserDataSynchroniser { - readonly resourceKey: ResourceKey; - readonly source: SyncSource; + readonly resource: SyncResource; readonly status: SyncStatus; readonly onDidChangeStatus: Event; readonly onDidChangeLocal: Event; @@ -276,13 +273,13 @@ export interface IUserDataSyncEnablementService { _serviceBrand: any; readonly onDidChangeEnablement: Event; - readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]>; + readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]>; isEnabled(): boolean; setEnablement(enabled: boolean): void; - isResourceEnabled(key: ResourceKey): boolean; - setResourceEnablement(key: ResourceKey, enabled: boolean): void; + isResourceEnabled(resource: SyncResource): boolean; + setResourceEnablement(resource: SyncResource, enabled: boolean): void; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); @@ -292,11 +289,11 @@ export interface IUserDataSyncService { readonly status: SyncStatus; readonly onDidChangeStatus: Event; - readonly conflictsSources: SyncSource[]; - readonly onDidChangeConflicts: Event; + readonly conflictsSources: SyncResource[]; + readonly onDidChangeConflicts: Event; - readonly onDidChangeLocal: Event; - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; + readonly onDidChangeLocal: Event; + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>; readonly lastSyncTime: number | undefined; readonly onDidChangeLastSyncTime: Event; @@ -309,7 +306,7 @@ export interface IUserDataSyncService { isFirstTimeSyncWithMerge(): Promise; resolveContent(resource: URI): Promise; - accept(source: SyncSource, content: string): Promise; + accept(source: SyncResource, content: string): Promise; } export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); @@ -351,50 +348,31 @@ export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; export const PREVIEW_QUERY = 'preview=true'; -export function toRemoteSyncResourceFromSource(source: SyncSource, ref?: string): URI { - return toRemoteSyncResource(getResourceKeyFromSyncSource(source), ref); +export function toRemoteSyncResource(resource: SyncResource, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resource}/${ref ? ref : 'latest'}` }); } -export function toRemoteSyncResource(resourceKey: ResourceKey, ref?: string): URI { - return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); -} -export function toLocalBackupSyncResource(resourceKey: ResourceKey, ref?: string): URI { - return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); +export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resource}/${ref ? ref : 'latest'}` }); } -export function resolveSyncResource(resource: URI): { remote: boolean, resourceKey: ResourceKey, ref?: string } | null { - const remote = resource.authority === 'remote'; - const resourceKey: ResourceKey = basename(dirname(resource)) as ResourceKey; - const ref = basename(resource); - if (resourceKey && ref) { - return { remote, resourceKey, ref: ref !== 'latest' ? ref : undefined }; +export function resolveSyncResource(resource: URI): { remote: boolean, resource: SyncResource, ref?: string } | null { + if (resource.scheme === USER_DATA_SYNC_SCHEME) { + const remote = resource.authority === 'remote'; + const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource; + const ref = basename(resource); + if (resourceKey && ref) { + return { remote, resource: resourceKey, ref: ref !== 'latest' ? ref : undefined }; + } } return null; } -export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncSource | undefined { +export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncResource | undefined { if (isEqual(uri, environmentService.settingsSyncPreviewResource)) { - return SyncSource.Settings; + return SyncResource.Settings; } if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) { - return SyncSource.Keybindings; + return SyncResource.Keybindings; } return undefined; } - -export function getResourceKeyFromSyncSource(source: SyncSource): ResourceKey { - switch (source) { - case SyncSource.Settings: return 'settings'; - case SyncSource.Keybindings: return 'keybindings'; - case SyncSource.Extensions: return 'extensions'; - case SyncSource.GlobalState: return 'globalState'; - } -} - -export function getSyncSourceFromResourceKey(resourceKey: ResourceKey): SyncSource { - switch (resourceKey) { - case 'settings': return SyncSource.Settings; - case 'keybindings': return SyncSource.Keybindings; - case 'extensions': return SyncSource.Extensions; - case 'globalState': return SyncSource.GlobalState; - } -} diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts index b1e2c50404..16a25c56c5 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserDataSyncLogService, ResourceKey, ALL_RESOURCE_KEYS, IUserDataSyncBackupStoreService, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, ALL_SYNC_RESOURCES, IUserDataSyncBackupStoreService, IResourceRefHandle, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { joinPath } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; @@ -23,11 +23,11 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, ) { super(); - ALL_RESOURCE_KEYS.forEach(resourceKey => this.cleanUpBackup(resourceKey)); + ALL_SYNC_RESOURCES.forEach(resourceKey => this.cleanUpBackup(resourceKey)); } - async getAllRefs(resourceKey: ResourceKey): Promise { - const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + async getAllRefs(resource: SyncResource): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resource); const stat = await this.fileService.resolve(folder); if (stat.children) { const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); @@ -39,22 +39,22 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD return []; } - async resolveContent(resourceKey: ResourceKey, ref?: string): Promise { + async resolveContent(resource: SyncResource, ref?: string): Promise { if (!ref) { - const refs = await this.getAllRefs(resourceKey); + const refs = await this.getAllRefs(resource); if (refs.length) { ref = refs[refs.length - 1].ref; } } if (ref) { - const file = joinPath(this.environmentService.userDataSyncHome, resourceKey, ref); + const file = joinPath(this.environmentService.userDataSyncHome, resource, ref); const content = await this.fileService.readFile(file); return content.value.toString(); } return null; } - async backup(resourceKey: ResourceKey, content: string): Promise { + async backup(resourceKey: SyncResource, content: string): Promise { const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); try { @@ -67,8 +67,8 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } catch (e) { /* Ignore */ } } - private async cleanUpBackup(resourceKey: ResourceKey): Promise { - const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + private async cleanUpBackup(resource: SyncResource): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resource); try { try { if (!(await this.fileService.exists(folder))) { diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index 6bfa779916..ed8521180b 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; @@ -14,7 +14,7 @@ type SyncEnablementClassification = { }; const enablementKey = 'sync.enable'; -function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; } +function getEnablementKey(resource: SyncResource) { return `${enablementKey}.${resource}`; } export class UserDataSyncEnablementService extends Disposable implements IUserDataSyncEnablementService { @@ -23,8 +23,8 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa private _onDidChangeEnablement = new Emitter(); readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; - private _onDidChangeResourceEnablement = new Emitter<[ResourceKey, boolean]>(); - readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; + private _onDidChangeResourceEnablement = new Emitter<[SyncResource, boolean]>(); + readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event; constructor( @IStorageService private readonly storageService: IStorageService, @@ -45,13 +45,13 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa } } - isResourceEnabled(resourceKey: ResourceKey): boolean { - return this.storageService.getBoolean(getEnablementKey(resourceKey), StorageScope.GLOBAL, true); + isResourceEnabled(resource: SyncResource): boolean { + return this.storageService.getBoolean(getEnablementKey(resource), StorageScope.GLOBAL, true); } - setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { - if (this.isResourceEnabled(resourceKey) !== enabled) { - const resourceEnablementKey = getEnablementKey(resourceKey); + setResourceEnablement(resource: SyncResource, enabled: boolean): void { + if (this.isResourceEnabled(resource) !== enabled) { + const resourceEnablementKey = getEnablementKey(resource); this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL); } @@ -63,7 +63,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa this._onDidChangeEnablement.fire(this.isEnabled()); return; } - const resourceKey = ALL_RESOURCE_KEYS.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; + const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; if (resourceKey) { this._onDidChangeResourceEnablement.fire([resourceKey, this.isEnabled()]); return; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 152a216054..de4ffa293a 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -35,16 +35,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - readonly onDidChangeLocal: Event; + readonly onDidChangeLocal: Event; - private _conflictsSources: SyncSource[] = []; - get conflictsSources(): SyncSource[] { return this._conflictsSources; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflictsSources: SyncResource[] = []; + get conflictsSources(): SyncResource[] { return this._conflictsSources; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - private _syncErrors: [SyncSource, UserDataSyncError][] = []; - private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _syncErrors: [SyncResource, UserDataSyncError][] = []; + private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } @@ -75,7 +75,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined); - this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.source))); + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.resource))); } async pull(): Promise { @@ -84,7 +84,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.pull(); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); } } this.updateLastSyncTime(); @@ -96,7 +96,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.push(); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); } } this.updateLastSyncTime(); @@ -129,10 +129,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ for (const synchroniser of this.synchronisers) { try { - await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined); + await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resource] : undefined); } catch (e) { - this.handleSyncError(e, synchroniser.source); - this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]); + this.handleSyncError(e, synchroniser.resource); + this._syncErrors.push([synchroniser.resource, UserDataSyncError.toUserDataSyncError(e)]); } } @@ -171,7 +171,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async accept(source: SyncSource, content: string): Promise { + async accept(source: SyncResource, content: string): Promise { await this.checkEnablement(); const synchroniser = this.getSynchroniser(source); await synchroniser.accept(content); @@ -180,7 +180,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ async resolveContent(resource: URI): Promise { const result = resolveSyncResource(resource); if (result) { - const synchronizer = this.synchronisers.filter(s => s.resourceKey === result.resourceKey)[0]; + const synchronizer = this.synchronisers.filter(s => s.resource === result.resource)[0]; if (synchronizer) { if (PREVIEW_QUERY === resource.query) { return result.remote ? synchronizer.getRemoteContentFromPreview() : null; @@ -216,7 +216,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { synchroniser.resetLocal(); } catch (e) { - this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); + this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); this.logService.error(e); } } @@ -291,7 +291,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private handleSyncError(e: Error, source: SyncSource): void { + private handleSyncError(e: Error, source: SyncResource): void { if (e instanceof UserDataSyncStoreError) { switch (e.code) { case UserDataSyncErrorCode.TooLarge: @@ -303,12 +303,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(`${source}: ${toErrorMessage(e)}`); } - private computeConflictsSources(): SyncSource[] { - return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.source); + private computeConflictsSources(): SyncResource[] { + return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.resource); } - getSynchroniser(source: SyncSource): IUserDataSynchroniser { - return this.synchronisers.filter(s => s.source === source)[0]; + getSynchroniser(source: SyncResource): IUserDataSynchroniser { + return this.synchronisers.filter(s => s.resource === source)[0]; } private async checkEnablement(): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 2438765138..b5ba165c8f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, ResourceKey, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request'; import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -31,12 +31,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); } - async getAllRefs(key: ResourceKey): Promise { + async getAllRefs(resource: SyncResource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const uri = joinPath(this.userDataSyncStore.url, 'resource', key); + const uri = joinPath(this.userDataSyncStore.url, 'resource', resource); const headers: IHeaders = {}; const context = await this.request({ type: 'GET', url: uri.toString(), headers }, undefined, CancellationToken.None); @@ -49,12 +49,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created })); } - async resolveContent(key: ResourceKey, ref: string): Promise { + async resolveContent(resource: SyncResource, ref: string): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key, ref).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource, ref).toString(); const headers: IHeaders = {}; const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); @@ -67,12 +67,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return content; } - async delete(key: ResourceKey): Promise { + async delete(resource: SyncResource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString(); const headers: IHeaders = {}; const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); @@ -82,12 +82,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } } - async read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise { + async read(resource: SyncResource, oldValue: IUserData | null): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key, 'latest').toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource, 'latest').toString(); const headers: IHeaders = {}; // Disable caching as they are cached by synchronisers headers['Cache-Control'] = 'no-cache'; @@ -95,7 +95,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None); + const context = await this.request({ type: 'GET', url, headers }, resource, CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -103,37 +103,37 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } if (!isSuccess(context)) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource); } const ref = context.res.headers['etag']; if (!ref) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source); + throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource); } const content = await asText(context); return { ref, content }; } - async write(key: ResourceKey, data: string, ref: string | null, source?: SyncSource): Promise { + async write(resource: SyncResource, data: string, ref: string | null): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; if (ref) { headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None); + const context = await this.request({ type: 'POST', url, data, headers }, resource, CancellationToken.None); if (!isSuccess(context)) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource); } const newRef = context.res.headers['etag']; if (!newRef) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source); + throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource); } return newRef; } @@ -169,7 +169,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } } - private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise { + private async request(options: IRequestOptions, source: SyncResource | undefined, token: CancellationToken): Promise { const authToken = await this.authTokenService.getToken(); if (!authToken) { throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 669c22fa4d..955306edb9 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncStoreService, IUserDataSyncService, SyncSource, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; @@ -44,7 +44,7 @@ suite('SettingsSync', () => { setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(); - testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncSource.Settings) as SettingsSynchroniser; + testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Settings) as SettingsSynchroniser; disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); }); @@ -77,7 +77,7 @@ suite('SettingsSync', () => { await updateSettings(expected); await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, expected); @@ -101,7 +101,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -132,7 +132,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -163,7 +163,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -187,7 +187,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -205,7 +205,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -239,7 +239,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -287,7 +287,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 386c187d33..e5f4c60be7 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ResourceKey, IUserDataSyncStoreService, SyncSource, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; @@ -17,7 +17,7 @@ class TestSynchroniser extends AbstractSynchroniser { syncResult: { status?: SyncStatus, error?: boolean } = {}; onDoSyncCall: Emitter = this._register(new Emitter()); - readonly resourceKey: ResourceKey = 'settings'; + readonly resource: SyncResource = SyncResource.Settings; protected readonly version: number = 1; private cancelled: boolean = false; @@ -40,7 +40,7 @@ class TestSynchroniser extends AbstractSynchroniser { } async apply(ref: string): Promise { - ref = await this.userDataSyncStoreService.write(this.resourceKey, '', ref); + ref = await this.userDataSyncStoreService.write(this.resource, '', ref); await this.updateLastSyncUserData({ ref, syncData: { content: '', version: this.version } }); } @@ -68,7 +68,7 @@ suite('TestSynchronizer', () => { teardown(() => disposableStore.clear()); test('status is syncing', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); const actual: SyncStatus[] = []; disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); @@ -85,7 +85,7 @@ suite('TestSynchronizer', () => { }); test('status is set correctly when sync is finished', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncBarrier.open(); const actual: SyncStatus[] = []; @@ -97,7 +97,7 @@ suite('TestSynchronizer', () => { }); test('status is set correctly when sync has conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncResult = { status: SyncStatus.HasConflicts }; testObject.syncBarrier.open(); @@ -110,7 +110,7 @@ suite('TestSynchronizer', () => { }); test('status is set correctly when sync has errors', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncResult = { error: true }; testObject.syncBarrier.open(); @@ -127,7 +127,7 @@ suite('TestSynchronizer', () => { }); test('sync should not run if syncing already', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); const promise = Event.toPromise(testObject.onDoSyncCall.event); testObject.sync(); @@ -144,8 +144,8 @@ suite('TestSynchronizer', () => { }); test('sync should not run if disabled', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); - client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resourceKey, false); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); + client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resource, false); const actual: SyncStatus[] = []; disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); @@ -157,7 +157,7 @@ suite('TestSynchronizer', () => { }); test('sync should not run if there are conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncResult = { status: SyncStatus.HasConflicts }; testObject.syncBarrier.open(); await testObject.sync(); @@ -171,7 +171,7 @@ suite('TestSynchronizer', () => { }); test('request latest data on precondition failure', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); // Sync once testObject.syncBarrier.open(); await testObject.sync(); @@ -186,13 +186,13 @@ suite('TestSynchronizer', () => { }); // Start sycing - const { ref } = await userDataSyncStoreService.read(testObject.resourceKey, null); + const { ref } = await userDataSyncStoreService.read(testObject.resource, null); await testObject.sync(ref); assert.deepEqual(server.requests, [ - { type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': ref } }, - { type: 'GET', url: `${server.url}/v1/resource/${testObject.resourceKey}/latest`, headers: {} }, - { type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': ref } }, + { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, ]); }); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 43005fc782..9f9256b477 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -120,8 +120,8 @@ export class UserDataSyncClient extends Disposable { return this.instantiationService.get(IUserDataSyncService).sync(); } - read(key: ResourceKey): Promise { - return this.instantiationService.get(IUserDataSyncStoreService).read(key, null); + read(resource: SyncResource): Promise { + return this.instantiationService.get(IUserDataSyncStoreService).read(resource, null); } } @@ -132,7 +132,7 @@ export class UserDataSyncTestServer implements IRequestService { readonly url: string = 'http://host:3000'; private session: string | null = null; - private readonly data: Map = new Map(); + private readonly data: Map = new Map(); private _requests: { url: string, type: string, headers?: IHeaders }[] = []; get requests(): { url: string, type: string, headers?: IHeaders }[] { return this._requests; } @@ -180,7 +180,7 @@ export class UserDataSyncTestServer implements IRequestService { private async getManifest(headers?: IHeaders): Promise { if (this.session) { - const latest: Record = Object.create({}); + const latest: Record = Object.create({}); const manifest: IUserDataManifest = { session: this.session, latest }; this.data.forEach((value, key) => latest[key] = value.ref); return this.toResponse(200, { 'Content-Type': 'application/json' }, JSON.stringify(manifest)); @@ -189,7 +189,7 @@ export class UserDataSyncTestServer implements IRequestService { } private async getLatestData(resource: string, headers: IHeaders = {}): Promise { - const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); if (resourceKey) { const data = this.data.get(resourceKey); if (!data) { @@ -210,7 +210,7 @@ export class UserDataSyncTestServer implements IRequestService { if (!this.session) { this.session = generateUuid(); } - const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); if (resourceKey) { const data = this.data.get(resourceKey); if (headers['If-Match'] !== (data ? data.ref : '0')) { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 49d51d7799..6f5a26b6ac 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IFileService } from 'vs/platform/files/common/files'; @@ -480,7 +480,7 @@ suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing te await testObject.sync(); assert.deepEqual(testObject.status, SyncStatus.HasConflicts); - assert.deepEqual(testObject.conflictsSources, [SyncSource.Settings]); + assert.deepEqual(testObject.conflictsSources, [SyncResource.Settings]); }); test('test sync will sync other non conflicted areas', async () => { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index aa6ff024e5..e502e50a25 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1802,9 +1802,9 @@ declare module 'vscode' { * A code that identifies this error. * * Possible values are names of errors, like [`FileNotFound`](#FileSystemError.FileNotFound), - * or `undefined` for an unspecified error. + * or `Unknown` for an unspecified error. */ - readonly code?: string; + readonly code: string; } //#endregion @@ -1824,4 +1824,24 @@ declare module 'vscode' { } //#endregion + + //#region https://github.com/microsoft/vscode/issues/92421 + + export enum ProgressLocation { + /** + * Show progress for a view, as progress bar inside the view (when visible), + * and as an overlay on the activity bar icon. Doesn't support cancellation or discrete progress. + */ + View = 25, + } + + export interface ProgressOptions { + /** + * The target view identifier for showing progress when using [ProgressLocation.View](#ProgressLocation.View). + */ + viewId?: string + } + + //#endregion + } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6883355204..3e3e5702ce 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -543,6 +543,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { + if (options.location === extHostTypes.ProgressLocation.View) { + checkProposedApiEnabled(extension); + } return extHostProgress.withProgress(extension, options, task); }, createOutputChannel(name: string): vscode.OutputChannel { diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index d1e42212e6..a00d96295f 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -24,9 +24,10 @@ export class ExtHostProgress implements ExtHostProgressShape { withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Thenable { const handle = this._handles++; - const { title, location, cancellable } = options; + const { title, location, cancellable, viewId } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); + + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location, viewId), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 5b93b7a43b..9910742d57 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1093,11 +1093,12 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation): MainProgressLocation { + export function from(loc: vscode.ProgressLocation, viewId?: string): MainProgressLocation | string { switch (loc) { case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; case types.ProgressLocation.Window: return MainProgressLocation.Window; case types.ProgressLocation.Notification: return MainProgressLocation.Notification; + case types.ProgressLocation.View: return viewId ?? ''; } throw new Error(`Unknown 'ProgressLocation'`); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 93bd4a714b..6d045d552b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2082,7 +2082,8 @@ export class Task implements vscode.Task2 { export enum ProgressLocation { SourceControl = 1, Window = 10, - Notification = 15 + Notification = 15, + View = 25 } @es5ClassCompat @@ -2333,12 +2334,12 @@ export class FileSystemError extends Error { return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable); } - readonly code?: string; + readonly code: string; constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) { super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage); - this.code = terminator?.name; + this.code = terminator?.name ?? 'Unknown'; // mark the error as file system provider error so that // we can extract the error code on the receiving side diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 60cc28662a..8173fe602e 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -83,19 +83,32 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro } private doGetEditorPickItems(): Array { - return this.doGetEditors().map(({ editor, groupId }) => { + return this.doGetEditors().map(({ editor, groupId }): IEditorQuickPickItem => { const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + const isDirty = editor.isDirty() && !editor.isSaving(); return { editor, groupId, resource, - label: editor.isDirty() && !editor.isSaving() ? `$(circle-filled) ${editor.getName()}` : editor.getName(), - ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()), + label: editor.getName(), + ariaLabel: localize('entryAriaLabel', "{0}, editors picker", editor.getName()), description: editor.getDescription(), iconClasses: getIconClasses(this.modelService, this.modeService, resource), italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), - accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor) + buttonsAlwaysVisible: isDirty, + buttons: [ + { + iconClass: isDirty ? 'codicon-circle-filled' : 'codicon-close', + tooltip: localize('closeEditor', "Close Editor") + } + ], + trigger: async () => { + await this.editorGroupService.getGroup(groupId)?.closeEditor(editor, { preserveFocus: true }); + + return TriggerAction.REFRESH_PICKER; + }, + accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor), }; }); } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5dfb8c231a..0efb9af467 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -21,6 +21,7 @@ import { flatten, mergeSort } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import Severity from 'vs/base/common/severity'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -583,7 +584,7 @@ export interface ITreeViewDataProvider { } export interface IEditableData { - validationMessage: (value: string) => string | null; + validationMessage: (value: string) => { content: string, severity: Severity } | null; placeholder?: string | null; startingValue?: string | null; onFinish: (value: string, success: boolean) => void; diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 41461a5e53..ded9285894 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { localize } from 'vs/nls'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; @@ -47,9 +47,18 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + config.launch.openConfigFile(false, false); + + return TriggerAction.CLOSE_PICKER; + }, accept: async () => { if (StartAction.isEnabled(this.debugService)) { this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); @@ -80,7 +89,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index fb811f4b4a..605550b3f5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -5,7 +5,6 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; @@ -34,6 +33,7 @@ import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cance import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { localize } from 'vs/nls'; export class DebugSession implements IDebugSession { @@ -232,7 +232,7 @@ export class DebugSession implements IDebugSession { */ async launchOrAttach(config: IConfig): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'launch or attach')); } // __sessionID only used for EH debugging (but we add it always for now...) @@ -250,7 +250,7 @@ export class DebugSession implements IDebugSession { */ async terminate(restart = false): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminate')); } this.cancelAllRequests(); @@ -266,7 +266,7 @@ export class DebugSession implements IDebugSession { */ async disconnect(restart = false): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'disconnect')); } this.cancelAllRequests(); @@ -278,7 +278,7 @@ export class DebugSession implements IDebugSession { */ async restart(): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restart')); } this.cancelAllRequests(); @@ -287,7 +287,7 @@ export class DebugSession implements IDebugSession { async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'breakpoints')); } if (!this.raw.readyForBreakpoints) { @@ -321,7 +321,7 @@ export class DebugSession implements IDebugSession { async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'function breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -338,7 +338,7 @@ export class DebugSession implements IDebugSession { async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'exception breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -348,10 +348,10 @@ export class DebugSession implements IDebugSession { async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'data breakpoints info')); } if (!this.raw.readyForBreakpoints) { - throw new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); + throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); } const response = await this.raw.dataBreakpointInfo({ name, variablesReference }); @@ -360,7 +360,7 @@ export class DebugSession implements IDebugSession { async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'data breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -377,7 +377,7 @@ export class DebugSession implements IDebugSession { async breakpointsLocations(uri: URI, lineNumber: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'breakpoints locations')); } const source = this.getRawSource(uri); @@ -393,7 +393,7 @@ export class DebugSession implements IDebugSession { customRequest(request: string, args: any): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", request)); } return this.raw.custom(request, args); @@ -401,7 +401,7 @@ export class DebugSession implements IDebugSession { stackTrace(threadId: number, startFrame: number, levels: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stackTrace')); } const token = this.getNewCancellationToken(threadId); @@ -410,7 +410,7 @@ export class DebugSession implements IDebugSession { async exceptionInfo(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'exceptionInfo')); } const response = await this.raw.exceptionInfo({ threadId }); @@ -428,7 +428,7 @@ export class DebugSession implements IDebugSession { scopes(frameId: number, threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'scopes')); } const token = this.getNewCancellationToken(threadId); @@ -437,7 +437,7 @@ export class DebugSession implements IDebugSession { variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'variables')); } const token = threadId ? this.getNewCancellationToken(threadId) : undefined; @@ -446,7 +446,7 @@ export class DebugSession implements IDebugSession { evaluate(expression: string, frameId: number, context?: string): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'evaluate')); } return this.raw.evaluate({ expression, frameId, context }); @@ -454,7 +454,7 @@ export class DebugSession implements IDebugSession { async restartFrame(frameId: number, threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restartFrame')); } await this.raw.restartFrame({ frameId }, threadId); @@ -462,7 +462,7 @@ export class DebugSession implements IDebugSession { async next(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'next')); } await this.raw.next({ threadId }); @@ -470,7 +470,7 @@ export class DebugSession implements IDebugSession { async stepIn(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepIn')); } await this.raw.stepIn({ threadId }); @@ -478,7 +478,7 @@ export class DebugSession implements IDebugSession { async stepOut(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepOut')); } await this.raw.stepOut({ threadId }); @@ -486,7 +486,7 @@ export class DebugSession implements IDebugSession { async stepBack(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepBack')); } await this.raw.stepBack({ threadId }); @@ -494,7 +494,7 @@ export class DebugSession implements IDebugSession { async continue(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'continue')); } await this.raw.continue({ threadId }); @@ -502,7 +502,7 @@ export class DebugSession implements IDebugSession { async reverseContinue(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'reverse continue')); } await this.raw.reverseContinue({ threadId }); @@ -510,7 +510,7 @@ export class DebugSession implements IDebugSession { async pause(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'pause')); } await this.raw.pause({ threadId }); @@ -518,7 +518,7 @@ export class DebugSession implements IDebugSession { async terminateThreads(threadIds?: number[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminateThreads')); } await this.raw.terminateThreads({ threadIds }); @@ -526,7 +526,7 @@ export class DebugSession implements IDebugSession { setVariable(variablesReference: number, name: string, value: string): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'setVariable')); } return this.raw.setVariable({ variablesReference, name, value }); @@ -534,7 +534,7 @@ export class DebugSession implements IDebugSession { gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'gotoTargets')); } return this.raw.gotoTargets({ source, line, column }); @@ -542,7 +542,7 @@ export class DebugSession implements IDebugSession { goto(threadId: number, targetId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'goto')); } return this.raw.goto({ threadId, targetId }); @@ -550,7 +550,7 @@ export class DebugSession implements IDebugSession { loadSource(resource: URI): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'loadSource'))); } const source = this.getSourceForUri(resource); @@ -568,7 +568,7 @@ export class DebugSession implements IDebugSession { async getLoadedSources(): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'getLoadedSources'))); } const response = await this.raw.loadedSources({}); @@ -581,7 +581,7 @@ export class DebugSession implements IDebugSession { async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'completions'))); } return this.raw.completions({ @@ -700,7 +700,7 @@ export class DebugSession implements IDebugSession { } this.rawListeners.push(this.raw.onDidInitialize(async () => { - aria.status(nls.localize('debuggingStarted', "Debugging started.")); + aria.status(localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = async () => { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { try { @@ -782,7 +782,7 @@ export class DebugSession implements IDebugSession { })); this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => { - aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); + aria.status(localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { await this.debugService.restartSession(this, event.body.restart); } else if (this.raw) { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 034e0d303f..ff18acb4cc 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -230,7 +230,7 @@ export class RawDebugSession implements IDisposable { */ async start(): Promise { if (!this.debugAdapter) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(nls.localize('noDebugAdapterStart', "No debug adapter, can not start debug session."))); } await this.debugAdapter.startSession(); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index e3dd44b692..b42b6306e0 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -754,18 +754,24 @@ export class ShowOpenedFileInNewWindow extends Action { } } -export function validateFileName(item: ExplorerItem, name: string): string | null { +export function validateFileName(item: ExplorerItem, name: string): { content: string, severity: Severity } | null { // Produce a well formed file name name = getWellFormedFileName(name); // Name not provided if (!name || name.length === 0 || /^\s+$/.test(name)) { - return nls.localize('emptyFileNameError', "A file or folder name must be provided."); + return { + content: nls.localize('emptyFileNameError', "A file or folder name must be provided."), + severity: Severity.Error + }; } // Relative paths only if (name[0] === '/' || name[0] === '\\') { - return nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash."); + return { + content: nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash."), + severity: Severity.Error + }; } const names = coalesce(name.split(/[\\/]/)); @@ -775,14 +781,27 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul // Do not allow to overwrite existing file const child = parent?.getChild(name); if (child && child !== item) { - return nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name); + return { + content: nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name), + severity: Severity.Error + }; } } // Invalid File name const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows; if (names.some((folderName) => !extpath.isValidBasename(folderName, windowsBasenameValidity))) { - return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)); + return { + content: nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)), + severity: Severity.Error + }; + } + + if (names.some(name => /^\s|\s$/.test(name))) { + return { + content: nls.localize('fileNameWhitespaceWarning', "Leading or trailing whitespace detected in file or folder name."), + severity: Severity.Warning + }; } return null; @@ -804,7 +823,7 @@ export function getWellFormedFileName(filename: string): string { // Trim tabs filename = strings.trim(filename, '\t'); - // Remove trailing dots, slashes, and spaces + // Remove trailing dots and slashes filename = strings.rtrim(filename, '.'); filename = strings.rtrim(filename, '/'); filename = strings.rtrim(filename, '\\'); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index d4054b8b46..f1233e5517 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -376,13 +376,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - const content = editableData.validationMessage(value); - if (!content) { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { return null; } return { - content, + content: message.content, formatContent: true, type: MessageType.ERROR }; @@ -392,10 +392,6 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing! - }); - const lastDot = value.lastIndexOf('.'); inputBox.value = value; @@ -412,8 +408,27 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + if (inputBox.isInputValid()) { + const message = editableData.validationMessage(inputBox.value); + if (message) { + inputBox.showMessage({ + content: message.content, + formatContent: true, + type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR + }); + } else { + inputBox.hideMessage(); + } + } + }; + showInputBoxNotification(); + const toDispose = [ inputBox, + inputBox.onDidChange(value => { + label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing! + }), DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { @@ -423,6 +438,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + showInputBoxNotification(); + }), DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { done(inputBox.isInputValid(), true); }), diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index ecd3771cb9..3db33ede3d 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -95,6 +95,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewsService.openView(view.id, true) }); @@ -110,6 +111,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewletService.openViewlet(viewlet.id, true) }); @@ -121,6 +123,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.panelService.openPanel(panel.id, true) }); @@ -137,8 +140,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { tab.terminalInstances.forEach((terminal, terminalIndex) => { + const label = localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title); viewEntries.push({ - label: localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title), + label, + ariaLabel: localize('viewPickAriaLabel', "{0}, view picker", label), containerLabel: localize('terminals', "Terminal"), accept: async () => { await this.terminalService.showPanel(true); @@ -152,8 +157,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.outputService.showChannel(channel.id) }); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index dae1e39dd1..d78b1ad4f5 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -28,7 +28,7 @@ import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/pl import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { once } from 'vs/base/common/functional'; @@ -282,13 +282,13 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { - const content = editableData.validationMessage(value); - if (!content) { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { return null; } return { - content, + content: message.content, formatContent: true, type: MessageType.ERROR }; @@ -657,6 +657,17 @@ export class TunnelPanelDescriptor implements IViewDescriptor { } } +function validationMessage(validationString: string | null): { content: string, severity: Severity } | null { + if (!validationString) { + return null; + } + + return { + content: validationString, + severity: Severity.Error + }; +} + namespace LabelTunnelAction { export const ID = 'remote.tunnel.label'; export const LABEL = nls.localize('remote.tunnel.label', "Set Label"); @@ -733,7 +744,7 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, null); }, - validationMessage: validateInput, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: forwardPrompt }); } @@ -916,7 +927,7 @@ namespace ChangeLocalPortAction { } } }, - validationMessage: validateInput, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: nls.localize('remote.tunnelsView.changePort', "New local port") }); } diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 797d52b795..abdf24b638 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { matchesFuzzy } from 'vs/base/common/filters'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; @@ -59,14 +59,12 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + for (const key of recentlyUsedTasks.keys()) { const task = taskMap[key]; if (task) { recent.push(task); } - }); - + } for (const task of tasks) { const key = task.getRecentlyUsedKey(); if (!key || !recentlyUsedTasks.has(key)) { @@ -83,13 +81,13 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider sorter.compare(a, b)); - this.fillPicks(taskPicks, filter, configured, localize('configured', 'configured tasks')); + this.fillPicks(taskPicks, filter, configured, localize('configured', "configured tasks")); detected.sort((a, b) => sorter.compare(a, b)); - this.fillPicks(taskPicks, filter, detected, localize('detected', 'detected tasks')); + this.fillPicks(taskPicks, filter, detected, localize('detected', "detected tasks")); return taskPicks; } @@ -107,7 +105,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { @@ -122,9 +120,6 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { - this.taskService.run(task, { attachProblemMatcher: true }); - }, trigger: () => { if (ContributedTask.is(task)) { this.taskService.customize(task, undefined, true); @@ -132,7 +127,10 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + this.taskService.run(task, { attachProblemMatcher: true }); } }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts new file mode 100644 index 0000000000..9aa372dd08 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; + +export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'term '; + + constructor( + @ITerminalService private readonly terminalService: ITerminalService, + @ICommandService private readonly commandService: ICommandService, + ) { + super(TerminalQuickAccessProvider.PREFIX); + } + + protected getPicks(filter: string): Array { + const terminalPicks: Array = []; + + const terminalTabs = this.terminalService.terminalTabs; + for (let tabIndex = 0; tabIndex < terminalTabs.length; tabIndex++) { + const terminalTab = terminalTabs[tabIndex]; + for (let terminalIndex = 0; terminalIndex < terminalTab.terminalInstances.length; terminalIndex++) { + const terminal = terminalTab.terminalInstances[terminalIndex]; + const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`; + + const highlights = matchesFuzzy(filter, label, true); + if (highlights) { + terminalPicks.push({ + label, + ariaLabel: localize('termEntryAriaLabel', "{0}, terminal picker", label), + highlights: { label: highlights }, + buttons: [ + { + iconClass: 'codicon-gear', + tooltip: localize('renameTerminal', "Rename Terminal") + }, + { + iconClass: 'codicon-trash', + tooltip: localize('killTerminal', "Kill Terminal Instance") + } + ], + trigger: buttonIndex => { + switch (buttonIndex) { + case 0: + this.commandService.executeCommand(TERMINAL_COMMAND_ID.RENAME, terminal); + return TriggerAction.NO_ACTION; + case 1: + terminal.dispose(true); + return TriggerAction.REFRESH_PICKER; + } + + return TriggerAction.NO_ACTION; + }, + accept: () => { + this.terminalService.setActiveInstance(terminal); + this.terminalService.showPanel(true); + } + }); + } + } + } + + if (terminalPicks.length > 0) { + terminalPicks.push({ type: 'separator' }); + } + + const createTerminalLabel = localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"); + terminalPicks.push({ + label: `$(plus) ${createTerminalLabel}`, + ariaLabel: localize('termEntryAriaLabel', "{0}, terminal picker", createTerminalLabel), + accept: () => this.commandService.executeCommand('workbench.action.terminal.new') + }); + + return terminalPicks; + + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 7a830ca068..82b7b06122 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -39,6 +39,8 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminaQuickAccess'; registerSingleton(ITerminalService, TerminalService, true); @@ -60,6 +62,16 @@ quickOpenRegistry.registerQuickOpenHandler( ) ); +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: TerminalQuickAccessProvider, + prefix: TerminalQuickAccessProvider.PREFIX, + contextKey: inTerminalsPicker, + placeholder: nls.localize('tasksQuickAccessPlaceholder', "Type the name of a terminal to open."), + helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Show All Opened Terminals"), needsEditor: false }] +}); + const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker'; CommandsRegistry.registerCommand( { id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) }); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index f0e5699d34..0c2473707c 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -24,7 +24,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline, TimelinePaneId } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { basename } from 'vs/base/common/path'; @@ -34,7 +34,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { fromNow } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; @@ -403,7 +403,7 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let timeline: Timeline | undefined; try { - timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result); + timeline = await this.progressService.withProgress({ location: this.id }, () => request.result); } finally { this._pendingRequests.delete(request.source); @@ -932,38 +932,35 @@ class TimelinePaneCommands extends Disposable { } })); - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: 'timeline.toggleFollowActiveEditor', - title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, - category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, - menu: [{ - id: MenuId.TimelineTitle, - command: { - // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), - icon: { id: 'codicon/eye' } - }, - group: 'navigation', - order: 98, - when: TimelineFollowActiveEditorContext - }, - { - id: MenuId.TimelineTitle, - command: { - // title: localize(`ToggleFollowActiveEditorCommand.follow`, "Follow the Active Editor"), - icon: { id: 'codicon/eye-closed' } - }, - group: 'navigation', - order: 98, - when: TimelineFollowActiveEditorContext.toNegated() - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - pane.followActiveEditor = !pane.followActiveEditor; - } - })); + this._register(CommandsRegistry.registerCommand('timeline.toggleFollowActiveEditor', + (accessor: ServicesAccessor, ...args: any[]) => pane.followActiveEditor = !pane.followActiveEditor + )); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, + // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), + icon: { id: 'codicon/eye' }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext + }))); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, + // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), + icon: { id: 'codicon/eye-closed' }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext.toNegated() + }))); this._register(timelineService.onDidChangeProviders(() => this.updateTimelineSourceFilters())); this.updateTimelineSourceFilters(); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5aab7b99c1..6790124e85 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -30,7 +30,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT, toRemoteSyncResourceFromSource, PREVIEW_QUERY, resolveSyncResource, getSyncSourceFromResourceKey } from 'vs/platform/userDataSync/common/userDataSync'; +import { CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT, PREVIEW_QUERY, resolveSyncResource, toRemoteSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -60,14 +60,14 @@ const enum AuthStatus { const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthStatus.Initializing); const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); -type ConfigureSyncQuickPickItem = { id: ResourceKey, label: string, description?: string }; +type ConfigureSyncQuickPickItem = { id: SyncResource, label: string, description?: string }; -function getSyncAreaLabel(source: SyncSource): string { +function getSyncAreaLabel(source: SyncResource): string { switch (source) { - case SyncSource.Settings: return localize('settings', "Settings"); - case SyncSource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); - case SyncSource.Extensions: return localize('extensions', "Extensions"); - case SyncSource.GlobalState: return localize('ui state label', "UI State"); + case SyncResource.Settings: return localize('settings', "Settings"); + case SyncResource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); + case SyncResource.Extensions: return localize('extensions', "Extensions"); + case SyncResource.GlobalState: return localize('ui state label', "UI State"); } } @@ -283,8 +283,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } - private readonly conflictsDisposables = new Map(); - private onDidChangeConflicts(conflicts: SyncSource[]) { + private readonly conflictsDisposables = new Map(); + private onDidChangeConflicts(conflicts: SyncResource[]) { this.updateBadge(); if (conflicts.length) { this.conflictsSources.set(this.userDataSyncService.conflictsSources.join(',')); @@ -352,22 +352,22 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async acceptRemote(syncSource: SyncSource) { + private async acceptRemote(syncResource: SyncResource) { try { - const contents = await this.userDataSyncService.resolveContent(toRemoteSyncResourceFromSource(syncSource).with({ query: PREVIEW_QUERY })); + const contents = await this.userDataSyncService.resolveContent(toRemoteSyncResource(syncResource).with({ query: PREVIEW_QUERY })); if (contents) { - await this.userDataSyncService.accept(syncSource, contents); + await this.userDataSyncService.accept(syncResource, contents); } } catch (e) { this.notificationService.error(e); } } - private async acceptLocal(syncSource: SyncSource): Promise { + private async acceptLocal(syncSource: SyncResource): Promise { try { - const previewResource = syncSource === SyncSource.Settings + const previewResource = syncSource === SyncResource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource - : syncSource === SyncSource.Keybindings + : syncSource === SyncResource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource : null; if (previewResource) { @@ -415,15 +415,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); return; case UserDataSyncErrorCode.TooLarge: - if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { - this.disableSync(error.source); - const sourceArea = getSyncAreaLabel(error.source); + if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) { + this.disableSync(error.resource); + const sourceArea = getSyncAreaLabel(error.resource); this.notificationService.notify({ severity: Severity.Error, message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea.toLowerCase(), sourceArea.toLowerCase(), '100kb'), actions: { primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true, - () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => error.resource === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); } @@ -438,8 +438,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private readonly invalidContentErrorDisposables = new Map(); - private onSyncErrors(errors: [SyncSource, UserDataSyncError][]): void { + private readonly invalidContentErrorDisposables = new Map(); + private onSyncErrors(errors: [SyncResource, UserDataSyncError][]): void { if (errors.length) { for (const [source, error] of errors) { switch (error.code) { @@ -460,14 +460,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private handleInvalidContentError(source: SyncSource): void { + private handleInvalidContentError(source: SyncResource): void { if (this.invalidContentErrorDisposables.has(source)) { return; } - if (source !== SyncSource.Settings && source !== SyncSource.Keybindings) { + if (source !== SyncResource.Settings && source !== SyncResource.Keybindings) { return; } - const resource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; + const resource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; if (isEqual(resource, toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }))) { // Do not show notification if the file in error is active return; @@ -478,7 +478,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea.toLowerCase()), actions: { primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => source === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); this.invalidContentErrorDisposables.set(source, toDisposable(() => { @@ -602,17 +602,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { return [{ - id: 'settings', - label: getSyncAreaLabel(SyncSource.Settings) + id: SyncResource.Settings, + label: getSyncAreaLabel(SyncResource.Settings) }, { - id: 'keybindings', - label: getSyncAreaLabel(SyncSource.Keybindings) + id: SyncResource.Keybindings, + label: getSyncAreaLabel(SyncResource.Keybindings) }, { - id: 'extensions', - label: getSyncAreaLabel(SyncSource.Extensions) + id: SyncResource.Extensions, + label: getSyncAreaLabel(SyncResource.Extensions) }, { - id: 'globalState', - label: getSyncAreaLabel(SyncSource.GlobalState), + id: SyncResource.GlobalState, + label: getSyncAreaLabel(SyncResource.GlobalState), description: localize('ui state description', "only 'Display Language' for now") }]; } @@ -707,15 +707,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private disableSync(source?: SyncSource): void { + private disableSync(source?: SyncResource): void { if (source === undefined) { this.userDataSyncEnablementService.setEnablement(false); } else { switch (source) { - case SyncSource.Settings: return this.userDataSyncEnablementService.setResourceEnablement('settings', false); - case SyncSource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement('keybindings', false); - case SyncSource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement('extensions', false); - case SyncSource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement('globalState', false); + case SyncResource.Settings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Settings, false); + case SyncResource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Keybindings, false); + case SyncResource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Extensions, false); + case SyncResource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.GlobalState, false); } } } @@ -729,9 +729,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined { - const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource - : source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource + private getConflictsEditorInput(source: SyncResource): IEditorInput | undefined { + const previewResource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource + : source === SyncResource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource : null; return previewResource ? this.editorService.editors.filter(input => input instanceof DiffEditorInput && isEqual(previewResource, input.master.resource))[0] : undefined; } @@ -743,18 +743,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private async handleConflicts(source: SyncSource): Promise { + private async handleConflicts(resource: SyncResource): Promise { let previewResource: URI | undefined = undefined; let label: string = ''; - if (source === SyncSource.Settings) { + if (resource === SyncResource.Settings) { previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource; label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); - } else if (source === SyncSource.Keybindings) { + } else if (resource === SyncResource.Keybindings) { previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource; label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); } if (previewResource) { - const remoteContentResource = toRemoteSyncResourceFromSource(source).with({ query: PREVIEW_QUERY }); + const remoteContentResource = toRemoteSyncResource(resource).with({ query: PREVIEW_QUERY }); await this.editorService.openEditor({ leftResource: remoteContentResource, rightResource: previewResource, @@ -846,7 +846,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerShowSettingsConflictsAction(): void { const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); - CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Settings)); + CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncResource.Settings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { @@ -873,7 +873,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerShowKeybindingsConflictsAction(): void { const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); - CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Keybindings)); + CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncResource.Keybindings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { @@ -934,10 +934,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (that.userDataSyncService.conflictsSources.length) { for (const source of that.userDataSyncService.conflictsSources) { switch (source) { - case SyncSource.Settings: + case SyncResource.Settings: items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title }); break; - case SyncSource.Keybindings: + case SyncResource.Keybindings: items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title }); break; } @@ -1130,7 +1130,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { - const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || getSyncSourceFromResourceKey(resolveSyncResource(model.uri)!.resourceKey))!; + const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || resolveSyncResource(model.uri)!.resource)!; this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); const syncAreaLabel = getSyncAreaLabel(conflictsSource); const result = await this.dialogService.confirm({ diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index 73600442ef..08e08f5895 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ALL_RESOURCE_KEYS, CONTEXT_SYNC_ENABLEMENT, IUserDataSyncStoreService, toRemoteSyncResource, resolveSyncResource, IUserDataSyncBackupStoreService, IResourceRefHandle, ResourceKey, toLocalBackupSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, CONTEXT_SYNC_ENABLEMENT, IUserDataSyncStoreService, toRemoteSyncResource, resolveSyncResource, IUserDataSyncBackupStoreService, IResourceRefHandle, toLocalBackupSyncResource, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; @@ -61,8 +61,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { if (visible && !treeView.dataProvider) { disposable.dispose(); treeView.dataProvider = this.instantiationService.createInstance(UserDataSyncHistoryViewDataProvider, id, - (resourceKey: ResourceKey) => remote ? this.userDataSyncStoreService.getAllRefs(resourceKey) : this.userDataSyncBackupStoreService.getAllRefs(resourceKey), - (resourceKey: ResourceKey, ref: string) => remote ? toRemoteSyncResource(resourceKey, ref) : toLocalBackupSyncResource(resourceKey, ref)); + (resource: SyncResource) => remote ? this.userDataSyncStoreService.getAllRefs(resource) : this.userDataSyncBackupStoreService.getAllRefs(resource), + (resource: SyncResource, ref: string) => remote ? toRemoteSyncResource(resource, ref) : toLocalBackupSyncResource(resource, ref)); } }); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); @@ -114,7 +114,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { let resource = URI.parse(handle.$treeItemHandle); const result = resolveSyncResource(resource); if (result) { - resource = resource.with({ fragment: result.resourceKey }); + resource = resource.with({ fragment: result.resource }); await editorService.openEditor({ resource }); } } @@ -152,8 +152,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { const resource = URI.parse(handle.$treeItemHandle); const result = resolveSyncResource(resource); if (result) { - const leftResource: URI = resource.with({ fragment: result.resourceKey }); - const rightResource: URI = result.resourceKey === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource; + const leftResource: URI = resource.with({ fragment: result.resource }); + const rightResource: URI = result.resource === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource; await editorService.openEditor({ leftResource, rightResource, @@ -174,8 +174,8 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { constructor( private readonly viewId: string, - private getAllRefs: (resourceKey: ResourceKey) => Promise, - private toResource: (resourceKey: ResourceKey, ref: string) => URI + private getAllRefs: (resource: SyncResource) => Promise, + private toResource: (resource: SyncResource, ref: string) => URI ) { } @@ -183,7 +183,7 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { if (element) { return this.getResources(element.handle); } - return ALL_RESOURCE_KEYS.map(resourceKey => ({ + return ALL_SYNC_RESOURCES.map(resourceKey => ({ handle: resourceKey, collapsibleState: TreeItemCollapsibleState.Collapsed, label: { label: resourceKey }, @@ -193,7 +193,7 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { } private async getResources(handle: string): Promise { - const resourceKey = ALL_RESOURCE_KEYS.filter(key => key === handle)[0]; + const resourceKey = ALL_SYNC_RESOURCES.filter(key => key === handle)[0]; if (resourceKey) { const refHandles = await this.getAllRefs(resourceKey); return refHandles.map(({ ref, created }) => { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 3abec099c3..783c3af4bd 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -280,16 +280,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); modelPromise = model.load(options); - // Install model listeners - const modelListeners = new DisposableStore(); - modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); - modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); - modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); - modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); - modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); - modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); - - this.mapResourceToModelListeners.set(resource, modelListeners); + this.registerModel(newModel); } // Store pending loads to avoid race conditions @@ -298,9 +289,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Make known to manager (if not already known) this.add(resource, model); - // Signal as event if we created the model + // Emit some events if we created the model if (didCreateModel) { this._onDidCreate.fire(model); + + // If the model is dirty right from the beginning, + // make sure to emit this as an event + if (model.isDirty()) { + this._onDidChangeDirty.fire(model); + } } try { @@ -335,6 +332,21 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } + private registerModel(model: TextFileEditorModel): void { + + // Install model listeners + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model, reason }))); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(model))); + modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: model, reason }))); + modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(model))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + + // Keep for disposal + this.mapResourceToModelListeners.set(model.resource, modelListeners); + } + add(resource: URI, model: TextFileEditorModel): void { const knownModel = this.mapResourceToModel.get(resource); if (knownModel === model) { diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index f25b349064..69897026ba 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -219,11 +219,13 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe } private registerModel(model: UntitledTextEditorModel): void { - const modelDisposables = new DisposableStore(); - modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); - modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); - modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); - modelDisposables.add(model.onDispose(() => this._onDidDispose.fire(model))); + + // Install model listeners + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelListeners.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + modelListeners.add(model.onDispose(() => this._onDidDispose.fire(model))); // Remove from cache on dispose Event.once(model.onDispose)(() => { @@ -232,11 +234,17 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe this.mapResourceToModel.delete(model.resource); // Listeners - modelDisposables.dispose(); + modelListeners.dispose(); }); // Add to cache this.mapResourceToModel.set(model.resource, model); + + // If the model is dirty right from the beginning, + // make sure to emit this as an event + if (model.isDirty()) { + this._onDidChangeDirty.fire(model); + } } } diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 7a7bec6027..6d8a18f8d8 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -14,6 +14,7 @@ import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRe import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; suite('Untitled text editors', () => { @@ -120,15 +121,23 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file })); + let onDidChangeDirtyModel: IUntitledTextEditorModel | undefined = undefined; + const listener = service.onDidChangeDirty(model => { + onDidChangeDirtyModel = model; + }); + + const model = service.create({ associatedResource: file }); + const untitled = instantiationService.createInstance(UntitledTextEditorInput, model); assert.ok(untitled.isDirty()); + assert.equal(model, onDidChangeDirtyModel); - const model = await untitled.resolve(); + const resolvedModel = await untitled.resolve(); - assert.ok(model.hasAssociatedFilePath); + assert.ok(resolvedModel.hasAssociatedFilePath); assert.equal(untitled.isDirty(), true); untitled.dispose(); + listener.dispose(); }); test('no longer dirty when content gets empty (not with associated resource)', async () => { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 0e7c666285..0de6a39fd5 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, ISettingsSyncService, IConflictSetting, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, ISettingsSyncService, IConflictSetting, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -17,7 +17,7 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ private readonly channel: IChannel; readonly resourceKey = 'settings'; - readonly source = SyncSource.Settings; + readonly resource = SyncResource.Settings; private _status: SyncStatus = SyncStatus.Uninitialized; get status(): SyncStatus { return this._status; } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts index b95859314b..af79f80499 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceKey, IResourceRefHandle, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IResourceRefHandle, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -19,16 +19,16 @@ export class UserDataSyncBackupStoreService implements IUserDataSyncBackupStoreS this.channel = sharedProcessService.getChannel('userDataSyncBackupStoreService'); } - backup(key: ResourceKey, content: string): Promise { + backup(key: SyncResource, content: string): Promise { return this.channel.call('backup', [key, content]); } - getAllRefs(key: ResourceKey): Promise { + getAllRefs(key: SyncResource): Promise { return this.channel.call('getAllRefs', [key]); } - resolveContent(key: ResourceKey, ref: string): Promise { + resolveContent(key: SyncResource, ref: string): Promise { return this.channel.call('resolveContent', [key, ref]); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index bdf1ecc387..86a2ae2e9b 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, SyncSource, IUserDataSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -23,20 +23,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - private _conflictsSources: SyncSource[] = []; - get conflictsSources(): SyncSource[] { return this._conflictsSources; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflictsSources: SyncResource[] = []; + get conflictsSources(): SyncResource[] { return this._conflictsSources; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; - private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; constructor( @ISharedProcessService sharedProcessService: ISharedProcessService @@ -52,7 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return userDataSyncChannel.listen(event, arg); } }; - this.channel.call<[SyncStatus, SyncSource[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { + this.channel.call<[SyncStatus, SyncResource[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { this.updateStatus(status); this.updateConflicts(conflicts); if (lastSyncTime) { @@ -61,8 +61,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); this._register(this.channel.listen('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); }); - this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); - this._register(this.channel.listen<[SyncSource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); } pull(): Promise { @@ -73,7 +73,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('sync'); } - accept(source: SyncSource, content: string): Promise { + accept(source: SyncResource, content: string): Promise { return this.channel.call('accept', [source, content]); } @@ -102,7 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._onDidChangeStatus.fire(status); } - private async updateConflicts(conflicts: SyncSource[]): Promise { + private async updateConflicts(conflicts: SyncResource[]): Promise { this._conflictsSources = conflicts; this._onDidChangeConflicts.fire(conflicts); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts index 30172ec831..de7df39fc7 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncSource, IUserDataSyncStoreService, IUserDataSyncStore, getUserDataSyncStore, ResourceKey, IUserData, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncResource, IUserDataSyncStoreService, IUserDataSyncStore, getUserDataSyncStore, IUserData, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -25,11 +25,11 @@ export class UserDataSyncStoreService implements IUserDataSyncStoreService { this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); } - read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise { + read(key: SyncResource, oldValue: IUserData | null, source?: SyncResource): Promise { throw new Error('Not Supported'); } - write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise { + write(key: SyncResource, content: string, ref: string | null, source?: SyncResource): Promise { throw new Error('Not Supported'); } @@ -41,15 +41,15 @@ export class UserDataSyncStoreService implements IUserDataSyncStoreService { throw new Error('Not Supported'); } - getAllRefs(key: ResourceKey): Promise { + getAllRefs(key: SyncResource): Promise { return this.channel.call('getAllRefs', [key]); } - resolveContent(key: ResourceKey, ref: string): Promise { + resolveContent(key: SyncResource, ref: string): Promise { return this.channel.call('resolveContent', [key, ref]); } - delete(key: ResourceKey): Promise { + delete(key: SyncResource): Promise { return this.channel.call('delete', [key]); }