Merge from vscode 1df23554b2e3d5f1efc6fbc76ee61d3f7f186c6d

This commit is contained in:
ADS Merger
2020-03-12 06:51:03 +00:00
parent a68a6b9e44
commit b5592959c7
56 changed files with 1091 additions and 558 deletions
@@ -188,6 +188,9 @@ export function activate(context: ExtensionContext) {
// handle content request // handle content request
client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { client.onRequest(VSCodeContentRequest.type, (uriPath: string) => {
const uri = Uri.parse(uriPath); 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') { if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return workspace.openTextDocument(uri).then(doc => { return workspace.openTextDocument(uri).then(doc => {
schemaDocuments[uri.toString()] = true; schemaDocuments[uri.toString()] = true;
+234
View File
@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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. * Return a hash value for an object.
*/ */
@@ -70,3 +72,235 @@ export class Hasher {
return this._value; 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;
}
}
+15 -17
View File
@@ -428,29 +428,27 @@ export function commonSuffixLength(a: string, b: string): number {
return len; return len;
} }
// --- unicode /**
// http://en.wikipedia.org/wiki/Surrogate_pair * See 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;
//}
export function isHighSurrogate(charCode: number): boolean { export function isHighSurrogate(charCode: number): boolean {
return (0xD800 <= charCode && charCode <= 0xDBFF); return (0xD800 <= charCode && charCode <= 0xDBFF);
} }
/**
* See http://en.wikipedia.org/wiki/Surrogate_pair
*/
export function isLowSurrogate(charCode: number): boolean { export function isLowSurrogate(charCode: number): boolean {
return (0xDC00 <= charCode && charCode <= 0xDFFF); 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` * 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) { if (isHighSurrogate(charCode) && offset + 1 < len) {
const nextCharCode = str.charCodeAt(offset + 1); const nextCharCode = str.charCodeAt(offset + 1);
if (isLowSurrogate(nextCharCode)) { if (isLowSurrogate(nextCharCode)) {
return ((charCode - 0xD800) << 10) + (nextCharCode - 0xDC00) + 0x10000; return computeCodePoint(charCode, nextCharCode);
} }
} }
return charCode; return charCode;
@@ -473,7 +471,7 @@ function getPrevCodePoint(str: string, offset: number): number {
if (isLowSurrogate(charCode) && offset > 1) { if (isLowSurrogate(charCode) && offset > 1) {
const prevCharCode = str.charCodeAt(offset - 2); const prevCharCode = str.charCodeAt(offset - 2);
if (isHighSurrogate(prevCharCode)) { if (isHighSurrogate(prevCharCode)) {
return ((prevCharCode - 0xD800) << 10) + (charCode - 0xDC00) + 0x10000; return computeCodePoint(prevCharCode, charCode);
} }
} }
return charCode; return charCode;
@@ -239,6 +239,7 @@
margin-right: 8px; 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 .quick-input-list-entry:hover .quick-input-list-entry-action-bar,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar { .quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar {
display: flex; display: flex;
@@ -185,6 +185,12 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
} else { } else {
dom.removeClass(data.entry, 'has-actions'); dom.removeClass(data.entry, 'has-actions');
} }
if (element.item.buttonsAlwaysVisible) {
dom.addClass(data.entry, 'always-visible-actions');
} else {
dom.removeClass(data.entry, 'always-visible-actions');
}
} }
disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void { disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void {
@@ -28,6 +28,11 @@ export interface IQuickPickItem {
italic?: boolean; italic?: boolean;
highlights?: IQuickPickItemHighlights; highlights?: IQuickPickItemHighlights;
buttons?: IQuickInputButton[]; buttons?: IQuickInputButton[];
/**
* Wether to always show the buttons. By default buttons
* are only visible when hovering over them with the mouse
*/
buttonsAlwaysVisible?: boolean;
picked?: boolean; picked?: boolean;
alwaysShow?: boolean; alwaysShow?: boolean;
} }
@@ -262,6 +267,7 @@ export interface IQuickInputButton {
/** iconPath or iconClass required */ /** iconPath or iconClass required */
iconClass?: string; iconClass?: string;
tooltip?: string; tooltip?: string;
alwaysShow?: boolean;
} }
export interface IQuickPickItemButtonEvent<T extends IQuickPickItem> { export interface IQuickPickItemButtonEvent<T extends IQuickPickItem> {
+25 -1
View File
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { hash } from 'vs/base/common/hash'; import { hash, StringSHA1 } from 'vs/base/common/hash';
suite('Hash', () => { suite('Hash', () => {
test('string', () => { test('string', () => {
@@ -53,4 +53,28 @@ suite('Hash', () => {
assert.notEqual(a, b); 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');
});
}); });
@@ -616,7 +616,15 @@ class RenderedViewLine implements IRenderedViewLine {
if (!r || r.length === 0) { if (!r || r.length === 0) {
return -1; 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<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { private _readRawVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
@@ -42,8 +42,8 @@ class MoveCaretLeftAction extends MoveCaretAction {
constructor() { constructor() {
super(true, { super(true, {
id: 'editor.action.moveCarretLeftAction', id: 'editor.action.moveCarretLeftAction',
label: nls.localize('caret.moveLeft', "Move Caret Left"), label: nls.localize('caret.moveLeft', "Move Selected Text Left"),
alias: 'Move Caret Left', alias: 'Move Selected Text Left',
precondition: EditorContextKeys.writable precondition: EditorContextKeys.writable
}); });
} }
@@ -53,8 +53,8 @@ class MoveCaretRightAction extends MoveCaretAction {
constructor() { constructor() {
super(false, { super(false, {
id: 'editor.action.moveCarretRightAction', id: 'editor.action.moveCarretRightAction',
label: nls.localize('caret.moveRight', "Move Caret Right"), label: nls.localize('caret.moveRight', "Move Selected Text Right"),
alias: 'Move Caret Right', alias: 'Move Selected Text Right',
precondition: EditorContextKeys.writable precondition: EditorContextKeys.writable
}); });
} }
@@ -330,7 +330,11 @@ class ShowAccessibilityHelpAction extends EditorAction {
kbOpts: { kbOpts: {
kbExpr: EditorContextKeys.focus, kbExpr: EditorContextKeys.focus,
primary: KeyMod.Alt | KeyCode.F1, primary: KeyMod.Alt | KeyCode.F1,
weight: KeybindingWeight.EditorContrib weight: KeybindingWeight.EditorContrib,
linux: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1,
secondary: [KeyMod.Alt | KeyCode.F1]
}
} }
}); });
} }
+7 -9
View File
@@ -378,7 +378,7 @@ export interface IAction2Options extends ICommandAction {
/** /**
* One or many menu items. * One or many menu items.
*/ */
menu?: OneOrN<{ id: MenuId } & Omit<IMenuItem, 'command'> & { command?: Partial<Omit<ICommandAction, 'id'>> }>; menu?: OneOrN<{ id: MenuId } & Omit<IMenuItem, 'command'>>;
/** /**
* One keybinding. * One keybinding.
@@ -401,7 +401,7 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable {
const disposables = new DisposableStore(); const disposables = new DisposableStore();
const action = new ctor(); const action = new ctor();
const { f1, menu: menus, keybinding, description, ...command } = action.desc; const { f1, menu, keybinding, description, ...command } = action.desc;
// command // command
disposables.add(CommandsRegistry.registerCommand({ disposables.add(CommandsRegistry.registerCommand({
@@ -411,14 +411,12 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable {
})); }));
// menu // menu
if (Array.isArray(menus)) { if (Array.isArray(menu)) {
for (let item of menus) { for (let item of menu) {
const { command: commandOverrides, ...menu } = item; disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command }, ...item }));
disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command, ...commandOverrides }, ...menu }));
} }
} else if (menus) { } else if (menu) {
const { command: commandOverrides, ...menu } = menus; disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command }, ...menu }));
disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command, ...commandOverrides }, ...menu }));
} }
if (f1) { if (f1) {
disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command })); disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command }));
+2 -1
View File
@@ -45,7 +45,8 @@ export const enum ProgressLocation {
Extensions = 5, Extensions = 5,
Window = 10, Window = 10,
Notification = 15, Notification = 15,
Dialog = 20 Dialog = 20,
View = 25
} }
export interface IProgressOptions { export interface IProgressOptions {
@@ -6,7 +6,6 @@
import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
@@ -22,7 +21,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { } constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { }
provide(picker: IQuickPick<IHelpQuickAccessPickItem>, token: CancellationToken): IDisposable { provide(picker: IQuickPick<IHelpQuickAccessPickItem>): IDisposable {
const disposables = new DisposableStore(); const disposables = new DisposableStore();
// Open a picker with the selected value if picked // 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 // Fill in all providers separated by editor/global scope
const { editorProviders, globalProviders } = this.getQuickAccessProviders(); const { editorProviders, globalProviders } = this.getQuickAccessProviders();
picker.items = editorProviders.length === 0 || globalProviders.length === 0 ? picker.items = editorProviders.length === 0 || globalProviders.length === 0 ?
@@ -57,7 +65,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
const globalProviders: IHelpQuickAccessPickItem[] = []; const globalProviders: IHelpQuickAccessPickItem[] = [];
const editorProviders: 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) { for (const helpEntry of provider.helpEntries) {
const prefix = helpEntry.prefix || provider.prefix; const prefix = helpEntry.prefix || provider.prefix;
const label = prefix || '\u2026' /* ... */; const label = prefix || '\u2026' /* ... */;
@@ -65,8 +73,8 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
(helpEntry.needsEditor ? editorProviders : globalProviders).push({ (helpEntry.needsEditor ? editorProviders : globalProviders).push({
prefix, prefix,
label, label,
description: helpEntry.description, ariaLabel: localize('entryAriaLabel', "{0}, quick access help picker", label),
ariaLabel: localize('entryAriaLabel', "{0}, picker help", label) description: helpEntry.description
}); });
} }
} }
@@ -144,6 +144,24 @@ Registry.add(Extensions.Quickaccess, new QuickAccessRegistry());
//#region Helper class for simple picker based providers //#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 { 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 * 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 * @param buttonIndex index of the button of the item that
* was clicked. * 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<TriggerAction>;
} }
export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem> implements IQuickAccessProvider { export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem> implements IQuickAccessProvider {
@@ -192,13 +211,18 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
} else { } else {
picker.busy = true; picker.busy = true;
try { try {
picker.items = await res; const items = await res;
if (token.isCancellationRequested) {
return;
}
picker.items = items;
} finally { } finally {
picker.busy = false; if (!token.isCancellationRequested) {
picker.busy = false;
}
} }
} }
this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token);
}; };
disposables.add(picker.onDidChangeValue(() => updatePickerItems())); disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems(); updatePickerItems();
@@ -213,13 +237,26 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
})); }));
// Trigger the pick with button index if button triggered // Trigger the pick with button index if button triggered
disposables.add(picker.onDidTriggerItemButton(({ button, item }) => { disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => {
if (typeof item.trigger === 'function') { if (typeof item.trigger === 'function') {
const buttonIndex = item.buttons?.indexOf(button) ?? -1; const buttonIndex = item.buttons?.indexOf(button) ?? -1;
if (buttonIndex >= 0) { if (buttonIndex >= 0) {
const hide = item.trigger(buttonIndex); const result = item.trigger(buttonIndex);
if (hide !== false) { const action = (typeof result === 'number') ? result : await result;
picker.hide();
if (token.isCancellationRequested) {
return;
}
switch (action) {
case TriggerAction.NO_ACTION:
break;
case TriggerAction.CLOSE_PICKER:
picker.hide();
break;
case TriggerAction.REFRESH_PICKER:
updatePickerItems();
break;
} }
} }
} }
@@ -433,6 +433,7 @@ function registerDefaultClassifications(): void {
registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]); registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]);
registerTokenStyleDefault('property.readonly', [['variable.other.constant.property']]);
} }
export function getTokenClassificationRegistry(): ITokenClassificationRegistry { export function getTokenClassificationRegistry(): ITokenClassificationRegistry {
@@ -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 { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri'; 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 { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname } from 'vs/base/common/resources'; import { joinPath, dirname } from 'vs/base/common/resources';
import { CancelablePromise } from 'vs/base/common/async'; import { CancelablePromise } from 'vs/base/common/async';
@@ -19,6 +19,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isString } from 'vs/base/common/types'; import { isString } from 'vs/base/common/types';
import { uppercaseFirstLetter } from 'vs/base/common/strings';
type SyncSourceClassification = { type SyncSourceClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -54,10 +55,10 @@ export abstract class AbstractSynchroniser extends Disposable {
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event; readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
protected readonly lastSyncResource: URI; protected readonly lastSyncResource: URI;
protected readonly syncResourceLogLabel: string;
constructor( constructor(
readonly source: SyncSource, readonly resource: SyncResource,
readonly resourceKey: ResourceKey,
@IFileService protected readonly fileService: IFileService, @IFileService protected readonly fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService, @IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService,
@@ -68,8 +69,9 @@ export abstract class AbstractSynchroniser extends Disposable {
@IConfigurationService protected readonly configurationService: IConfigurationService, @IConfigurationService protected readonly configurationService: IConfigurationService,
) { ) {
super(); super();
this.syncFolder = joinPath(environmentService.userDataSyncHome, source); this.syncResourceLogLabel = uppercaseFirstLetter(this.resource);
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resourceKey}.json`); this.syncFolder = joinPath(environmentService.userDataSyncHome, resource);
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`);
} }
protected setStatus(status: SyncStatus): void { protected setStatus(status: SyncStatus): void {
@@ -79,32 +81,32 @@ export abstract class AbstractSynchroniser extends Disposable {
this._onDidChangStatus.fire(status); this._onDidChangStatus.fire(status);
if (status === SyncStatus.HasConflicts) { if (status === SyncStatus.HasConflicts) {
// Log to telemetry when there is a sync conflict // 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) { if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) {
// Log to telemetry when conflicts are resolved // 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<void> { async sync(ref?: string): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
if (this.status === SyncStatus.HasConflicts) { 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; return;
} }
if (this.status === SyncStatus.Syncing) { 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; 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); this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
@@ -114,9 +116,9 @@ export abstract class AbstractSynchroniser extends Disposable {
try { try {
status = await this.doSync(remoteUserData, lastSyncUserData); status = await this.doSync(remoteUserData, lastSyncUserData);
if (status === SyncStatus.HasConflicts) { 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) { } 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 { } finally {
this.setStatus(status); this.setStatus(status);
@@ -126,8 +128,8 @@ export abstract class AbstractSynchroniser extends Disposable {
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
// current version is not compatible with cloud version // current version is not compatible with cloud version
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: 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.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); 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 { try {
const status = await this.performSync(remoteUserData, lastSyncUserData); const status = await this.performSync(remoteUserData, lastSyncUserData);
@@ -137,7 +139,7 @@ export abstract class AbstractSynchroniser extends Disposable {
switch (e.code) { switch (e.code) {
case UserDataSyncErrorCode.RemotePreconditionFailed: case UserDataSyncErrorCode.RemotePreconditionFailed:
// Rejected as there is a new remote version. Syncing again, // 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 // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624
remoteUserData = await this.getRemoteUserData(null); remoteUserData = await this.getRemoteUserData(null);
return this.doSync(remoteUserData, lastSyncUserData); return this.doSync(remoteUserData, lastSyncUserData);
@@ -163,7 +165,7 @@ export abstract class AbstractSynchroniser extends Disposable {
} }
async getLocalBackupContent(ref?: string): Promise<string | null> { async getLocalBackupContent(ref?: string): Promise<string | null> {
return this.userDataSyncBackupStoreService.resolveContent(this.resourceKey, ref); return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref);
} }
async resetLocal(): Promise<void> { async resetLocal(): Promise<void> {
@@ -225,23 +227,23 @@ export abstract class AbstractSynchroniser extends Disposable {
private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise<IUserData> { private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise<IUserData> {
if (isString(refOrLastSyncData)) { 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 }; return { ref: refOrLastSyncData, content };
} else { } else {
const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null; 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<IRemoteUserData> { protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
const syncData: ISyncData = { version: this.version, content }; 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 }; return { ref, syncData };
} }
protected async backupLocal(content: string): Promise<void> { protected async backupLocal(content: string): Promise<void> {
const syncData: ISyncData = { version: this.version, content }; 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; protected abstract readonly version: number;
@@ -264,8 +266,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
constructor( constructor(
protected readonly file: URI, protected readonly file: URI,
source: SyncSource, resource: SyncResource,
resourceKey: ResourceKey,
@IFileService fileService: IFileService, @IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService, @IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@@ -275,14 +276,14 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
@IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncLogService logService: IUserDataSyncLogService,
@IConfigurationService configurationService: IConfigurationService, @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.watch(dirname(file)));
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
} }
async stop(): Promise<void> { async stop(): Promise<void> {
this.cancel(); this.cancel();
this.logService.trace(`${this.source}: Stopped synchronizing ${this.source.toLowerCase()}.`); this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
try { try {
await this.fileService.del(this.conflictsPreviewResource); await this.fileService.del(this.conflictsPreviewResource);
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
@@ -362,8 +363,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
constructor( constructor(
file: URI, file: URI,
source: SyncSource, resource: SyncResource,
resourceKey: ResourceKey,
@IFileService fileService: IFileService, @IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService, @IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@@ -374,7 +374,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
@IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService,
@IConfigurationService configurationService: IConfigurationService, @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 { protected hasErrors(content: string): boolean {
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -50,7 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService telemetryService: ITelemetryService, @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( this._register(
Event.debounce( Event.debounce(
Event.any<any>( Event.any<any>(
@@ -62,14 +62,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
async pull(): Promise<void> { async pull(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('Extensions: Started pulling extensions...'); this.logService.info(`${this.syncResourceLogLabel}: Started pulling extensions...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>(); const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
@@ -84,10 +84,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// No remote exists to pull // No remote exists to pull
else { 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -95,14 +95,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
async push(): Promise<void> { async push(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('Extensions: Started pushing extensions...'); this.logService.info(`${this.syncResourceLogLabel}: Started pushing extensions...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const localExtensions = await this.getLocalExtensions(); const localExtensions = await this.getLocalExtensions();
@@ -111,7 +111,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true); 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -148,7 +148,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
} }
accept(content: string): Promise<void> { accept(content: string): Promise<void> {
throw new Error('Extensions: Conflicts should not occur'); throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
} }
async hasLocalData(): Promise<boolean> { async hasLocalData(): Promise<boolean> {
@@ -177,9 +177,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const localExtensions = await this.getLocalExtensions(); const localExtensions = await this.getLocalExtensions();
if (remoteExtensions) { if (remoteExtensions) {
this.logService.trace('Extensions: Merging remote extensions with local extensions...'); this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`);
} else { } 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()); 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; const hasChanges = added.length || removed.length || updated.length || remote;
if (!hasChanges) { 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) { if (added.length || removed.length || updated.length) {
@@ -208,17 +208,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (remote) { if (remote) {
// update remote // update remote
this.logService.trace('Extensions: Updating remote extensions...'); this.logService.trace(`${this.syncResourceLogLabel}: Updating remote extensions...`);
const content = JSON.stringify(remote); const content = JSON.stringify(remote);
remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); 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) { if (lastSyncUserData?.ref !== remoteUserData.ref) {
// update last sync // 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 }); 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 installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
await Promise.all(extensionsToRemove.map(async extensionToRemove => { 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); 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); removeFromSkipped.push(extensionToRemove.identifier);
})); }));
} }
@@ -245,13 +245,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// Builtin Extension: Sync only enablement state // Builtin Extension: Sync only enablement state
if (installedExtension && installedExtension.type === ExtensionType.System) { if (installedExtension && installedExtension.type === ExtensionType.System) {
if (e.disabled) { 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); 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 { } 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); 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); removeFromSkipped.push(e.identifier);
return; return;
@@ -261,19 +261,19 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (extension) { if (extension) {
try { try {
if (e.disabled) { 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); 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 { } 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); 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 // Install only if the extension does not exist
if (!installedExtension || installedExtension.manifest.version !== extension.version) { 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); 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); removeFromSkipped.push(extension.identifier);
} }
} catch (error) { } catch (error) {
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -41,21 +41,21 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService configurationService: IConfigurationService, @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(this.fileService.watch(dirname(this.environmentService.argvResource)));
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
} }
async pull(): Promise<void> { async pull(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('UI State: Started pulling ui state...'); this.logService.info(`${this.syncResourceLogLabel}: Started pulling ui state...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
@@ -69,10 +69,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
// No remote exists to pull // No remote exists to pull
else { 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -80,14 +80,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
async push(): Promise<void> { async push(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('UI State: Started pushing UI State...'); this.logService.info(`${this.syncResourceLogLabel}: Started pushing UI State...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const localUserData = await this.getLocalGlobalState(); const localUserData = await this.getLocalGlobalState();
@@ -95,7 +95,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
await this.apply({ local: undefined, remote: localUserData, remoteUserData, localUserData, lastSyncUserData }, true); 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -132,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
} }
accept(content: string): Promise<void> { accept(content: string): Promise<void> {
throw new Error('UI State: Conflicts should not occur'); throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
} }
async hasLocalData(): Promise<boolean> { async hasLocalData(): Promise<boolean> {
@@ -160,9 +160,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const localGloablState = await this.getLocalGlobalState(); const localGloablState = await this.getLocalGlobalState();
if (remoteGlobalState) { 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 { } 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); const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState);
@@ -175,30 +175,30 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const hasChanges = local || remote; const hasChanges = local || remote;
if (!hasChanges) { 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) { if (local) {
// update 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.backupLocal(JSON.stringify(localUserData));
await this.writeLocalGlobalState(local); await this.writeLocalGlobalState(local);
this.logService.info('UI State: Updated local ui state'); this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`);
} }
if (remote) { if (remote) {
// update 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); const content = JSON.stringify(remote);
remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); 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) { if (lastSyncUserData?.ref !== remoteUserData.ref) {
// update last sync // 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); await this.updateLastSyncUserData(remoteUserData);
this.logService.info('UI State: Updated last synchronized ui state'); this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`);
} }
} }
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; 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 { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json'; import { parse } from 'vs/base/common/json';
@@ -43,19 +43,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
@ITelemetryService telemetryService: ITelemetryService, @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<void> { async pull(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('Keybindings: Started pulling keybindings...'); this.logService.info(`${this.syncResourceLogLabel}: Started pulling keybindings...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
@@ -78,10 +78,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
// No remote exists to pull // No remote exists to pull
else { 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -90,14 +90,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
async push(): Promise<void> { async push(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('Keybindings: Started pushing keybindings...'); this.logService.info(`${this.syncResourceLogLabel}: Started pushing keybindings...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const fileContent = await this.getLocalFileContent(); const fileContent = await this.getLocalFileContent();
@@ -119,10 +119,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
// No local exists to push // No local exists to push
else { 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -202,7 +202,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
switch (e.code) { switch (e.code) {
case UserDataSyncErrorCode.LocalPreconditionFailed: case UserDataSyncErrorCode.LocalPreconditionFailed:
// Rejected as there is a new local version. Syncing again. // 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); return this.performSync(remoteUserData, lastSyncUserData);
} }
} }
@@ -219,21 +219,21 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (content !== null) { if (content !== null) {
if (this.hasErrors(content)) { 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) { 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.backupLocal(this.toSyncContent(content, null));
await this.updateLocalFileContent(content, fileContent); await this.updateLocalFileContent(content, fileContent);
this.logService.info('Keybindings: Updated local keybindings'); this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
} }
if (hasRemoteChanged) { 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); const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null);
remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); 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 // Delete the preview
@@ -241,14 +241,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
await this.fileService.del(this.conflictsPreviewResource); await this.fileService.del(this.conflictsPreviewResource);
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
} else { } 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)) { 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); const lastSyncContent = this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null);
await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } }); 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; this.syncPreviewResultPromise = null;
@@ -276,14 +276,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (remoteContent) { if (remoteContent) {
const localContent: string = fileContent ? fileContent.value.toString() : '[]'; const localContent: string = fileContent ? fileContent.value.toString() : '[]';
if (this.hasErrors(localContent)) { 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 if (!lastSyncContent // First time sync
|| lastSyncContent !== localContent // Local has forwarded || lastSyncContent !== localContent // Local has forwarded
|| lastSyncContent !== remoteContent // Remote 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); const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService);
// Sync only if there are changes // Sync only if there are changes
if (result.hasChanges) { if (result.hasChanges) {
@@ -297,7 +297,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
// First time syncing to remote // First time syncing to remote
else if (fileContent) { 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(); content = fileContent.value.toString();
hasRemoteChanged = true; hasRemoteChanged = true;
} }
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; 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 { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json'; import { parse } from 'vs/base/common/json';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
@@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @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 { protected setStatus(status: SyncStatus): void {
@@ -78,14 +78,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
async pull(): Promise<void> { async pull(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('Settings: Started pulling settings...'); this.logService.info(`${this.syncResourceLogLabel}: Started pulling settings...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
@@ -113,10 +113,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// No remote exists to pull // No remote exists to pull
else { 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -124,14 +124,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
async push(): Promise<void> { async push(): Promise<void> {
if (!this.isEnabled()) { 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; return;
} }
this.stop(); this.stop();
try { try {
this.logService.info('Settings: Started pushing settings...'); this.logService.info(`${this.syncResourceLogLabel}: Started pushing settings...`);
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const fileContent = await this.getLocalFileContent(); const fileContent = await this.getLocalFileContent();
@@ -159,10 +159,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// No local exists to push // No local exists to push
else { 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 { } finally {
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
@@ -268,7 +268,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
switch (e.code) { switch (e.code) {
case UserDataSyncErrorCode.LocalPreconditionFailed: case UserDataSyncErrorCode.LocalPreconditionFailed:
// Rejected as there is a new local version. Syncing again. // 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); return this.performSync(remoteUserData, lastSyncUserData, resolvedConflicts);
} }
} }
@@ -288,10 +288,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
this.validateContent(content); this.validateContent(content);
if (hasLocalChanged) { 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.backupLocal(JSON.stringify(this.toSettingsSyncContent(content)));
await this.updateLocalFileContent(content, fileContent); await this.updateLocalFileContent(content, fileContent);
this.logService.info('Settings: Updated local settings'); this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`);
} }
if (hasRemoteChanged) { if (hasRemoteChanged) {
const formatUtils = await this.getFormattingOptions(); const formatUtils = await this.getFormattingOptions();
@@ -299,9 +299,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
const ignoredSettings = await this.getIgnoredSettings(content); const ignoredSettings = await this.getIgnoredSettings(content);
content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); 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); 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 // Delete the preview
@@ -309,13 +309,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
await this.fileService.del(this.conflictsPreviewResource); await this.fileService.del(this.conflictsPreviewResource);
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
} else { } 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) { 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); await this.updateLastSyncUserData(remoteUserData);
this.logService.info('Settings: Updated last synchronized settings'); this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized settings`);
} }
this.syncPreviewResultPromise = null; this.syncPreviewResultPromise = null;
@@ -343,7 +343,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (remoteSettingsSyncContent) { if (remoteSettingsSyncContent) {
const localContent: string = fileContent ? fileContent.value.toString() : '{}'; const localContent: string = fileContent ? fileContent.value.toString() : '{}';
this.validateContent(localContent); 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 ignoredSettings = await this.getIgnoredSettings();
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions);
content = result.localContent || result.remoteContent; content = result.localContent || result.remoteContent;
@@ -355,7 +355,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// First time syncing to remote // First time syncing to remote
else if (fileContent) { 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(); content = fileContent.value.toString();
hasRemoteChanged = true; hasRemoteChanged = true;
} }
@@ -406,7 +406,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
private validateContent(content: string): void { private validateContent(content: string): void {
if (this.hasErrors(content)) { 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);
} }
} }
} }
@@ -135,11 +135,16 @@ export function getUserDataSyncStore(productService: IProductService, configurat
return undefined; return undefined;
} }
export const ALL_RESOURCE_KEYS: ResourceKey[] = ['settings', 'keybindings', 'extensions', 'globalState']; export const enum SyncResource {
export type ResourceKey = 'settings' | 'keybindings' | 'extensions' | 'globalState'; 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 { export interface IUserDataManifest {
latest?: Record<ResourceKey, string> latest?: Record<SyncResource, string>
session: string; session: string;
} }
@@ -152,21 +157,21 @@ export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreServi
export interface IUserDataSyncStoreService { export interface IUserDataSyncStoreService {
_serviceBrand: undefined; _serviceBrand: undefined;
readonly userDataSyncStore: IUserDataSyncStore | undefined; readonly userDataSyncStore: IUserDataSyncStore | undefined;
read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise<IUserData>; read(resource: SyncResource, oldValue: IUserData | null): Promise<IUserData>;
write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise<string>; write(resource: SyncResource, content: string, ref: string | null): Promise<string>;
manifest(): Promise<IUserDataManifest | null>; manifest(): Promise<IUserDataManifest | null>;
clear(): Promise<void>; clear(): Promise<void>;
getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]>; getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]>;
resolveContent(key: ResourceKey, ref: string): Promise<string | null>; resolveContent(resource: SyncResource, ref: string): Promise<string | null>;
delete(key: ResourceKey): Promise<void>; delete(resource: SyncResource): Promise<void>;
} }
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService'); export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
export interface IUserDataSyncBackupStoreService { export interface IUserDataSyncBackupStoreService {
_serviceBrand: undefined; _serviceBrand: undefined;
backup(resourceKey: ResourceKey, content: string): Promise<void>; backup(resource: SyncResource, content: string): Promise<void>;
getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]>; getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]>;
resolveContent(key: ResourceKey, ref?: string): Promise<string | null>; resolveContent(resource: SyncResource, ref?: string): Promise<string | null>;
} }
//#endregion //#endregion
@@ -195,9 +200,9 @@ export enum UserDataSyncErrorCode {
export class UserDataSyncError extends Error { 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); super(message);
this.name = `${this.code} (UserDataSyncError) ${this.source}`; this.name = `${this.code} (UserDataSyncError) ${this.resource}`;
} }
static toUserDataSyncError(error: Error): UserDataSyncError { static toUserDataSyncError(error: Error): UserDataSyncError {
@@ -206,7 +211,7 @@ export class UserDataSyncError extends Error {
} }
const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name); const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name);
if (match && match[1]) { if (match && match[1]) {
return new UserDataSyncError(error.message, <UserDataSyncErrorCode>match[1], <SyncSource>match[2]); return new UserDataSyncError(error.message, <UserDataSyncErrorCode>match[1], <SyncResource>match[2]);
} }
return new UserDataSyncError(error.message, UserDataSyncErrorCode.Unknown); return new UserDataSyncError(error.message, UserDataSyncErrorCode.Unknown);
} }
@@ -230,13 +235,6 @@ export interface IGlobalState {
storage: IStringDictionary<any>; storage: IStringDictionary<any>;
} }
export const enum SyncSource {
Settings = 'Settings',
Keybindings = 'Keybindings',
Extensions = 'Extensions',
GlobalState = 'GlobalState'
}
export const enum SyncStatus { export const enum SyncStatus {
Uninitialized = 'uninitialized', Uninitialized = 'uninitialized',
Idle = 'idle', Idle = 'idle',
@@ -246,8 +244,7 @@ export const enum SyncStatus {
export interface IUserDataSynchroniser { export interface IUserDataSynchroniser {
readonly resourceKey: ResourceKey; readonly resource: SyncResource;
readonly source: SyncSource;
readonly status: SyncStatus; readonly status: SyncStatus;
readonly onDidChangeStatus: Event<SyncStatus>; readonly onDidChangeStatus: Event<SyncStatus>;
readonly onDidChangeLocal: Event<void>; readonly onDidChangeLocal: Event<void>;
@@ -276,13 +273,13 @@ export interface IUserDataSyncEnablementService {
_serviceBrand: any; _serviceBrand: any;
readonly onDidChangeEnablement: Event<boolean>; readonly onDidChangeEnablement: Event<boolean>;
readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]>; readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]>;
isEnabled(): boolean; isEnabled(): boolean;
setEnablement(enabled: boolean): void; setEnablement(enabled: boolean): void;
isResourceEnabled(key: ResourceKey): boolean; isResourceEnabled(resource: SyncResource): boolean;
setResourceEnablement(key: ResourceKey, enabled: boolean): void; setResourceEnablement(resource: SyncResource, enabled: boolean): void;
} }
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService'); export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
@@ -292,11 +289,11 @@ export interface IUserDataSyncService {
readonly status: SyncStatus; readonly status: SyncStatus;
readonly onDidChangeStatus: Event<SyncStatus>; readonly onDidChangeStatus: Event<SyncStatus>;
readonly conflictsSources: SyncSource[]; readonly conflictsSources: SyncResource[];
readonly onDidChangeConflicts: Event<SyncSource[]>; readonly onDidChangeConflicts: Event<SyncResource[]>;
readonly onDidChangeLocal: Event<SyncSource>; readonly onDidChangeLocal: Event<SyncResource>;
readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>;
readonly lastSyncTime: number | undefined; readonly lastSyncTime: number | undefined;
readonly onDidChangeLastSyncTime: Event<number>; readonly onDidChangeLastSyncTime: Event<number>;
@@ -309,7 +306,7 @@ export interface IUserDataSyncService {
isFirstTimeSyncWithMerge(): Promise<boolean>; isFirstTimeSyncWithMerge(): Promise<boolean>;
resolveContent(resource: URI): Promise<string | null>; resolveContent(resource: URI): Promise<string | null>;
accept(source: SyncSource, content: string): Promise<void>; accept(source: SyncResource, content: string): Promise<void>;
} }
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService'); export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
@@ -351,50 +348,31 @@ export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled',
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
export const PREVIEW_QUERY = 'preview=true'; export const PREVIEW_QUERY = 'preview=true';
export function toRemoteSyncResourceFromSource(source: SyncSource, ref?: string): URI { export function toRemoteSyncResource(resource: SyncResource, ref?: string): URI {
return toRemoteSyncResource(getResourceKeyFromSyncSource(source), ref); return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resource}/${ref ? ref : 'latest'}` });
} }
export function toRemoteSyncResource(resourceKey: ResourceKey, ref?: string): URI { export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): URI {
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resource}/${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 resolveSyncResource(resource: URI): { remote: boolean, resourceKey: ResourceKey, ref?: string } | null { export function resolveSyncResource(resource: URI): { remote: boolean, resource: SyncResource, ref?: string } | null {
const remote = resource.authority === 'remote'; if (resource.scheme === USER_DATA_SYNC_SCHEME) {
const resourceKey: ResourceKey = basename(dirname(resource)) as ResourceKey; const remote = resource.authority === 'remote';
const ref = basename(resource); const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource;
if (resourceKey && ref) { const ref = basename(resource);
return { remote, resourceKey, ref: ref !== 'latest' ? ref : undefined }; if (resourceKey && ref) {
return { remote, resource: resourceKey, ref: ref !== 'latest' ? ref : undefined };
}
} }
return null; 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)) { if (isEqual(uri, environmentService.settingsSyncPreviewResource)) {
return SyncSource.Settings; return SyncResource.Settings;
} }
if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) { if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) {
return SyncSource.Keybindings; return SyncResource.Keybindings;
} }
return undefined; 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;
}
}
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle'; 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 { joinPath } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService, IFileStat } from 'vs/platform/files/common/files'; 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, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
) { ) {
super(); super();
ALL_RESOURCE_KEYS.forEach(resourceKey => this.cleanUpBackup(resourceKey)); ALL_SYNC_RESOURCES.forEach(resourceKey => this.cleanUpBackup(resourceKey));
} }
async getAllRefs(resourceKey: ResourceKey): Promise<IResourceRefHandle[]> { async getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]> {
const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); const folder = joinPath(this.environmentService.userDataSyncHome, resource);
const stat = await this.fileService.resolve(folder); const stat = await this.fileService.resolve(folder);
if (stat.children) { if (stat.children) {
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); 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 []; return [];
} }
async resolveContent(resourceKey: ResourceKey, ref?: string): Promise<string | null> { async resolveContent(resource: SyncResource, ref?: string): Promise<string | null> {
if (!ref) { if (!ref) {
const refs = await this.getAllRefs(resourceKey); const refs = await this.getAllRefs(resource);
if (refs.length) { if (refs.length) {
ref = refs[refs.length - 1].ref; ref = refs[refs.length - 1].ref;
} }
} }
if (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); const content = await this.fileService.readFile(file);
return content.value.toString(); return content.value.toString();
} }
return null; return null;
} }
async backup(resourceKey: ResourceKey, content: string): Promise<void> { async backup(resourceKey: SyncResource, content: string): Promise<void> {
const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey);
const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`);
try { try {
@@ -67,8 +67,8 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD
} catch (e) { /* Ignore */ } } catch (e) { /* Ignore */ }
} }
private async cleanUpBackup(resourceKey: ResourceKey): Promise<void> { private async cleanUpBackup(resource: SyncResource): Promise<void> {
const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); const folder = joinPath(this.environmentService.userDataSyncHome, resource);
try { try {
try { try {
if (!(await this.fileService.exists(folder))) { if (!(await this.fileService.exists(folder))) {
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
@@ -14,7 +14,7 @@ type SyncEnablementClassification = {
}; };
const enablementKey = 'sync.enable'; 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 { export class UserDataSyncEnablementService extends Disposable implements IUserDataSyncEnablementService {
@@ -23,8 +23,8 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
private _onDidChangeEnablement = new Emitter<boolean>(); private _onDidChangeEnablement = new Emitter<boolean>();
readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event; readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
private _onDidChangeResourceEnablement = new Emitter<[ResourceKey, boolean]>(); private _onDidChangeResourceEnablement = new Emitter<[SyncResource, boolean]>();
readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event;
constructor( constructor(
@IStorageService private readonly storageService: IStorageService, @IStorageService private readonly storageService: IStorageService,
@@ -45,13 +45,13 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
} }
} }
isResourceEnabled(resourceKey: ResourceKey): boolean { isResourceEnabled(resource: SyncResource): boolean {
return this.storageService.getBoolean(getEnablementKey(resourceKey), StorageScope.GLOBAL, true); return this.storageService.getBoolean(getEnablementKey(resource), StorageScope.GLOBAL, true);
} }
setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { setResourceEnablement(resource: SyncResource, enabled: boolean): void {
if (this.isResourceEnabled(resourceKey) !== enabled) { if (this.isResourceEnabled(resource) !== enabled) {
const resourceEnablementKey = getEnablementKey(resourceKey); const resourceEnablementKey = getEnablementKey(resource);
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled });
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL); this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL);
} }
@@ -63,7 +63,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
this._onDidChangeEnablement.fire(this.isEnabled()); this._onDidChangeEnablement.fire(this.isEnabled());
return; 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) { if (resourceKey) {
this._onDidChangeResourceEnablement.fire([resourceKey, this.isEnabled()]); this._onDidChangeResourceEnablement.fire([resourceKey, this.isEnabled()]);
return; return;
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
@@ -35,16 +35,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>()); private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event; readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
readonly onDidChangeLocal: Event<SyncSource>; readonly onDidChangeLocal: Event<SyncResource>;
private _conflictsSources: SyncSource[] = []; private _conflictsSources: SyncResource[] = [];
get conflictsSources(): SyncSource[] { return this._conflictsSources; } get conflictsSources(): SyncResource[] { return this._conflictsSources; }
private _onDidChangeConflicts: Emitter<SyncSource[]> = this._register(new Emitter<SyncSource[]>()); private _onDidChangeConflicts: Emitter<SyncResource[]> = this._register(new Emitter<SyncResource[]>());
readonly onDidChangeConflicts: Event<SyncSource[]> = this._onDidChangeConflicts.event; readonly onDidChangeConflicts: Event<SyncResource[]> = this._onDidChangeConflicts.event;
private _syncErrors: [SyncSource, UserDataSyncError][] = []; private _syncErrors: [SyncResource, UserDataSyncError][] = [];
private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event;
private _lastSyncTime: number | undefined = undefined; private _lastSyncTime: number | undefined = undefined;
get lastSyncTime(): number | undefined { return this._lastSyncTime; } 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._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<void> { async pull(): Promise<void> {
@@ -84,7 +84,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
try { try {
await synchroniser.pull(); await synchroniser.pull();
} catch (e) { } catch (e) {
this.handleSyncError(e, synchroniser.source); this.handleSyncError(e, synchroniser.resource);
} }
} }
this.updateLastSyncTime(); this.updateLastSyncTime();
@@ -96,7 +96,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
try { try {
await synchroniser.push(); await synchroniser.push();
} catch (e) { } catch (e) {
this.handleSyncError(e, synchroniser.source); this.handleSyncError(e, synchroniser.resource);
} }
} }
this.updateLastSyncTime(); this.updateLastSyncTime();
@@ -129,10 +129,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
for (const synchroniser of this.synchronisers) { for (const synchroniser of this.synchronisers) {
try { 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) { } catch (e) {
this.handleSyncError(e, synchroniser.source); this.handleSyncError(e, synchroniser.resource);
this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]); 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<void> { async accept(source: SyncResource, content: string): Promise<void> {
await this.checkEnablement(); await this.checkEnablement();
const synchroniser = this.getSynchroniser(source); const synchroniser = this.getSynchroniser(source);
await synchroniser.accept(content); await synchroniser.accept(content);
@@ -180,7 +180,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async resolveContent(resource: URI): Promise<string | null> { async resolveContent(resource: URI): Promise<string | null> {
const result = resolveSyncResource(resource); const result = resolveSyncResource(resource);
if (result) { 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 (synchronizer) {
if (PREVIEW_QUERY === resource.query) { if (PREVIEW_QUERY === resource.query) {
return result.remote ? synchronizer.getRemoteContentFromPreview() : null; return result.remote ? synchronizer.getRemoteContentFromPreview() : null;
@@ -216,7 +216,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
try { try {
synchroniser.resetLocal(); synchroniser.resetLocal();
} catch (e) { } catch (e) {
this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`);
this.logService.error(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) { if (e instanceof UserDataSyncStoreError) {
switch (e.code) { switch (e.code) {
case UserDataSyncErrorCode.TooLarge: case UserDataSyncErrorCode.TooLarge:
@@ -303,12 +303,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.logService.error(`${source}: ${toErrorMessage(e)}`); this.logService.error(`${source}: ${toErrorMessage(e)}`);
} }
private computeConflictsSources(): SyncSource[] { private computeConflictsSources(): SyncResource[] {
return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.source); return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.resource);
} }
getSynchroniser(source: SyncSource): IUserDataSynchroniser { getSynchroniser(source: SyncResource): IUserDataSynchroniser {
return this.synchronisers.filter(s => s.source === source)[0]; return this.synchronisers.filter(s => s.resource === source)[0];
} }
private async checkEnablement(): Promise<void> { private async checkEnablement(): Promise<void> {
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle'; 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 { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request';
import { joinPath, relativePath } from 'vs/base/common/resources'; import { joinPath, relativePath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
@@ -31,12 +31,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
} }
async getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]> { async getAllRefs(resource: SyncResource): Promise<IResourceRefHandle[]> {
if (!this.userDataSyncStore) { if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.'); 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 headers: IHeaders = {};
const context = await this.request({ type: 'GET', url: uri.toString(), headers }, undefined, CancellationToken.None); 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 })); return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created }));
} }
async resolveContent(key: ResourceKey, ref: string): Promise<string | null> { async resolveContent(resource: SyncResource, ref: string): Promise<string | null> {
if (!this.userDataSyncStore) { if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.'); 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 headers: IHeaders = {};
const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); 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; return content;
} }
async delete(key: ResourceKey): Promise<void> { async delete(resource: SyncResource): Promise<void> {
if (!this.userDataSyncStore) { if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.'); 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 headers: IHeaders = {};
const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); 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<IUserData> { async read(resource: SyncResource, oldValue: IUserData | null): Promise<IUserData> {
if (!this.userDataSyncStore) { if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.'); 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 = {}; const headers: IHeaders = {};
// Disable caching as they are cached by synchronisers // Disable caching as they are cached by synchronisers
headers['Cache-Control'] = 'no-cache'; headers['Cache-Control'] = 'no-cache';
@@ -95,7 +95,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
headers['If-None-Match'] = oldValue.ref; 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) { if (context.res.statusCode === 304) {
// There is no new value. Hence return the old value. // There is no new value. Hence return the old value.
@@ -103,37 +103,37 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
} }
if (!isSuccess(context)) { 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']; const ref = context.res.headers['etag'];
if (!ref) { 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); const content = await asText(context);
return { ref, content }; return { ref, content };
} }
async write(key: ResourceKey, data: string, ref: string | null, source?: SyncSource): Promise<string> { async write(resource: SyncResource, data: string, ref: string | null): Promise<string> {
if (!this.userDataSyncStore) { if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.'); 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' }; const headers: IHeaders = { 'Content-Type': 'text/plain' };
if (ref) { if (ref) {
headers['If-Match'] = 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)) { 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']; const newRef = context.res.headers['etag'];
if (!newRef) { 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; return newRef;
} }
@@ -169,7 +169,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
} }
} }
private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise<IRequestContext> { private async request(options: IRequestOptions, source: SyncResource | undefined, token: CancellationToken): Promise<IRequestContext> {
const authToken = await this.authTokenService.getToken(); const authToken = await this.authTokenService.getToken();
if (!authToken) { if (!authToken) {
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source); throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source);
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; 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 { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
@@ -44,7 +44,7 @@ suite('SettingsSync', () => {
setup(async () => { setup(async () => {
client = disposableStore.add(new UserDataSyncClient(server)); client = disposableStore.add(new UserDataSyncClient(server));
await client.setUp(); 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())); disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear()));
}); });
@@ -77,7 +77,7 @@ suite('SettingsSync', () => {
await updateSettings(expected); await updateSettings(expected);
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@@ -101,7 +101,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -132,7 +132,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -163,7 +163,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -187,7 +187,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -205,7 +205,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -239,7 +239,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -287,7 +287,7 @@ suite('SettingsSync', () => {
await testObject.sync(); await testObject.sync();
const { content } = await client.read(testObject.resourceKey); const { content } = await client.read(testObject.resource);
assert.ok(content !== null); assert.ok(content !== null);
const actual = parseSettings(content!); const actual = parseSettings(content!);
assert.deepEqual(actual, `{ assert.deepEqual(actual, `{
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; 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 { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
@@ -17,7 +17,7 @@ class TestSynchroniser extends AbstractSynchroniser {
syncResult: { status?: SyncStatus, error?: boolean } = {}; syncResult: { status?: SyncStatus, error?: boolean } = {};
onDoSyncCall: Emitter<void> = this._register(new Emitter<void>()); onDoSyncCall: Emitter<void> = this._register(new Emitter<void>());
readonly resourceKey: ResourceKey = 'settings'; readonly resource: SyncResource = SyncResource.Settings;
protected readonly version: number = 1; protected readonly version: number = 1;
private cancelled: boolean = false; private cancelled: boolean = false;
@@ -40,7 +40,7 @@ class TestSynchroniser extends AbstractSynchroniser {
} }
async apply(ref: string): Promise<void> { async apply(ref: string): Promise<void> {
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 } }); await this.updateLastSyncUserData({ ref, syncData: { content: '', version: this.version } });
} }
@@ -68,7 +68,7 @@ suite('TestSynchronizer', () => {
teardown(() => disposableStore.clear()); teardown(() => disposableStore.clear());
test('status is syncing', async () => { 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[] = []; const actual: SyncStatus[] = [];
disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status)));
@@ -85,7 +85,7 @@ suite('TestSynchronizer', () => {
}); });
test('status is set correctly when sync is finished', async () => { 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(); testObject.syncBarrier.open();
const actual: SyncStatus[] = []; const actual: SyncStatus[] = [];
@@ -97,7 +97,7 @@ suite('TestSynchronizer', () => {
}); });
test('status is set correctly when sync has conflicts', async () => { 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.syncResult = { status: SyncStatus.HasConflicts };
testObject.syncBarrier.open(); testObject.syncBarrier.open();
@@ -110,7 +110,7 @@ suite('TestSynchronizer', () => {
}); });
test('status is set correctly when sync has errors', async () => { 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.syncResult = { error: true };
testObject.syncBarrier.open(); testObject.syncBarrier.open();
@@ -127,7 +127,7 @@ suite('TestSynchronizer', () => {
}); });
test('sync should not run if syncing already', async () => { 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); const promise = Event.toPromise(testObject.onDoSyncCall.event);
testObject.sync(); testObject.sync();
@@ -144,8 +144,8 @@ suite('TestSynchronizer', () => {
}); });
test('sync should not run if disabled', async () => { test('sync should not run if disabled', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resourceKey, false); client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resource, false);
const actual: SyncStatus[] = []; const actual: SyncStatus[] = [];
disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status)));
@@ -157,7 +157,7 @@ suite('TestSynchronizer', () => {
}); });
test('sync should not run if there are conflicts', async () => { 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.syncResult = { status: SyncStatus.HasConflicts };
testObject.syncBarrier.open(); testObject.syncBarrier.open();
await testObject.sync(); await testObject.sync();
@@ -171,7 +171,7 @@ suite('TestSynchronizer', () => {
}); });
test('request latest data on precondition failure', async () => { 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 // Sync once
testObject.syncBarrier.open(); testObject.syncBarrier.open();
await testObject.sync(); await testObject.sync();
@@ -186,13 +186,13 @@ suite('TestSynchronizer', () => {
}); });
// Start sycing // Start sycing
const { ref } = await userDataSyncStoreService.read(testObject.resourceKey, null); const { ref } = await userDataSyncStoreService.read(testObject.resource, null);
await testObject.sync(ref); await testObject.sync(ref);
assert.deepEqual(server.requests, [ assert.deepEqual(server.requests, [
{ type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': ref } }, { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': ref } },
{ type: 'GET', url: `${server.url}/v1/resource/${testObject.resourceKey}/latest`, headers: {} }, { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/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': `${parseInt(ref) + 1}` } },
]); ]);
}); });
@@ -6,7 +6,7 @@
import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestService } from 'vs/platform/request/common/request';
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation'; 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 { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
@@ -120,8 +120,8 @@ export class UserDataSyncClient extends Disposable {
return this.instantiationService.get(IUserDataSyncService).sync(); return this.instantiationService.get(IUserDataSyncService).sync();
} }
read(key: ResourceKey): Promise<IUserData> { read(resource: SyncResource): Promise<IUserData> {
return this.instantiationService.get(IUserDataSyncStoreService).read(key, null); return this.instantiationService.get(IUserDataSyncStoreService).read(resource, null);
} }
} }
@@ -132,7 +132,7 @@ export class UserDataSyncTestServer implements IRequestService {
readonly url: string = 'http://host:3000'; readonly url: string = 'http://host:3000';
private session: string | null = null; private session: string | null = null;
private readonly data: Map<ResourceKey, IUserData> = new Map<ResourceKey, IUserData>(); private readonly data: Map<SyncResource, IUserData> = new Map<SyncResource, IUserData>();
private _requests: { url: string, type: string, headers?: IHeaders }[] = []; private _requests: { url: string, type: string, headers?: IHeaders }[] = [];
get requests(): { url: string, type: string, headers?: IHeaders }[] { return this._requests; } 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<IRequestContext> { private async getManifest(headers?: IHeaders): Promise<IRequestContext> {
if (this.session) { if (this.session) {
const latest: Record<ResourceKey, string> = Object.create({}); const latest: Record<SyncResource, string> = Object.create({});
const manifest: IUserDataManifest = { session: this.session, latest }; const manifest: IUserDataManifest = { session: this.session, latest };
this.data.forEach((value, key) => latest[key] = value.ref); this.data.forEach((value, key) => latest[key] = value.ref);
return this.toResponse(200, { 'Content-Type': 'application/json' }, JSON.stringify(manifest)); 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<IRequestContext> { private async getLatestData(resource: string, headers: IHeaders = {}): Promise<IRequestContext> {
const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource);
if (resourceKey) { if (resourceKey) {
const data = this.data.get(resourceKey); const data = this.data.get(resourceKey);
if (!data) { if (!data) {
@@ -210,7 +210,7 @@ export class UserDataSyncTestServer implements IRequestService {
if (!this.session) { if (!this.session) {
this.session = generateUuid(); this.session = generateUuid();
} }
const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource);
if (resourceKey) { if (resourceKey) {
const data = this.data.get(resourceKey); const data = this.data.get(resourceKey);
if (headers['If-Match'] !== (data ? data.ref : '0')) { if (headers['If-Match'] !== (data ? data.ref : '0')) {
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; 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 { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore } from 'vs/base/common/lifecycle'; import { DisposableStore } from 'vs/base/common/lifecycle';
import { IFileService } from 'vs/platform/files/common/files'; import { IFileService } from 'vs/platform/files/common/files';
@@ -480,7 +480,7 @@ suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing te
await testObject.sync(); await testObject.sync();
assert.deepEqual(testObject.status, SyncStatus.HasConflicts); 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 () => { test('test sync will sync other non conflicted areas', async () => {
+22 -2
View File
@@ -1802,9 +1802,9 @@ declare module 'vscode' {
* A code that identifies this error. * A code that identifies this error.
* *
* Possible values are names of errors, like [`FileNotFound`](#FileSystemError.FileNotFound), * 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 //#endregion
@@ -1824,4 +1824,24 @@ declare module 'vscode' {
} }
//#endregion //#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
} }
@@ -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*/ } })); return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } }));
}, },
withProgress<R>(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable<R>) { withProgress<R>(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable<R>) {
if (options.location === extHostTypes.ProgressLocation.View) {
checkProposedApiEnabled(extension);
}
return extHostProgress.withProgress(extension, options, task); return extHostProgress.withProgress(extension, options, task);
}, },
createOutputChannel(name: string): vscode.OutputChannel { createOutputChannel(name: string): vscode.OutputChannel {
@@ -24,9 +24,10 @@ export class ExtHostProgress implements ExtHostProgressShape {
withProgress<R>(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>): Thenable<R> { withProgress<R>(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress<IProgressStep>, token: CancellationToken) => Thenable<R>): Thenable<R> {
const handle = this._handles++; 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); 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); return this._withProgress(handle, task, !!cancellable);
} }
@@ -1093,11 +1093,12 @@ export namespace EndOfLine {
} }
export namespace ProgressLocation { export namespace ProgressLocation {
export function from(loc: vscode.ProgressLocation): MainProgressLocation { export function from(loc: vscode.ProgressLocation, viewId?: string): MainProgressLocation | string {
switch (loc) { switch (loc) {
case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm;
case types.ProgressLocation.Window: return MainProgressLocation.Window; case types.ProgressLocation.Window: return MainProgressLocation.Window;
case types.ProgressLocation.Notification: return MainProgressLocation.Notification; case types.ProgressLocation.Notification: return MainProgressLocation.Notification;
case types.ProgressLocation.View: return viewId ?? '';
} }
throw new Error(`Unknown 'ProgressLocation'`); throw new Error(`Unknown 'ProgressLocation'`);
} }
+4 -3
View File
@@ -2082,7 +2082,8 @@ export class Task implements vscode.Task2 {
export enum ProgressLocation { export enum ProgressLocation {
SourceControl = 1, SourceControl = 1,
Window = 10, Window = 10,
Notification = 15 Notification = 15,
View = 25
} }
@es5ClassCompat @es5ClassCompat
@@ -2333,12 +2334,12 @@ export class FileSystemError extends Error {
return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable); return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable);
} }
readonly code?: string; readonly code: string;
constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) { constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) {
super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage); 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 // mark the error as file system provider error so that
// we can extract the error code on the receiving side // we can extract the error code on the receiving side
@@ -5,7 +5,7 @@
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; 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 { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -83,19 +83,32 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
} }
private doGetEditorPickItems(): Array<IEditorQuickPickItem> { private doGetEditorPickItems(): Array<IEditorQuickPickItem> {
return this.doGetEditors().map(({ editor, groupId }) => { return this.doGetEditors().map(({ editor, groupId }): IEditorQuickPickItem => {
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
const isDirty = editor.isDirty() && !editor.isSaving();
return { return {
editor, editor,
groupId, groupId,
resource, resource,
label: editor.isDirty() && !editor.isSaving() ? `$(circle-filled) ${editor.getName()}` : editor.getName(), label: editor.getName(),
ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()), ariaLabel: localize('entryAriaLabel', "{0}, editors picker", editor.getName()),
description: editor.getDescription(), description: editor.getDescription(),
iconClasses: getIconClasses(this.modelService, this.modeService, resource), iconClasses: getIconClasses(this.modelService, this.modeService, resource),
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), 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),
}; };
}); });
} }
+2 -1
View File
@@ -21,6 +21,7 @@ import { flatten, mergeSort } from 'vs/base/common/arrays';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { SetMap } from 'vs/base/common/collections'; import { SetMap } from 'vs/base/common/collections';
import { IProgressIndicator } from 'vs/platform/progress/common/progress'; 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'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
@@ -583,7 +584,7 @@ export interface ITreeViewDataProvider {
} }
export interface IEditableData { export interface IEditableData {
validationMessage: (value: string) => string | null; validationMessage: (value: string) => { content: string, severity: Severity } | null;
placeholder?: string | null; placeholder?: string | null;
startingValue?: string | null; startingValue?: string | null;
onFinish: (value: string, success: boolean) => void; onFinish: (value: string, success: boolean) => void;
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; 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 { localize } from 'vs/nls';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
@@ -47,9 +47,18 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
// Launch entry // Launch entry
picks.push({ picks.push({
label: config.name, label: config.name,
ariaLabel: localize('entryAriaLabel', "{0}, debug", config.name), ariaLabel: localize('entryAriaLabel', "{0}, debug picker", config.name),
description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? config.launch.name : '', description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? config.launch.name : '',
highlights: { label: highlights }, highlights: { label: highlights },
buttons: [{
iconClass: 'codicon-gear',
tooltip: localize('customizeTask', "Configure Launch Configuration")
}],
trigger: () => {
config.launch.openConfigFile(false, false);
return TriggerAction.CLOSE_PICKER;
},
accept: async () => { accept: async () => {
if (StartAction.isEnabled(this.debugService)) { if (StartAction.isEnabled(this.debugService)) {
this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name);
@@ -80,7 +89,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
// Add Config entry // Add Config entry
picks.push({ picks.push({
label, label,
ariaLabel: localize('entryAriaLabel', "{0}, debug", label), ariaLabel: localize('entryAriaLabel', "{0}, debug picker", label),
description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '', description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '',
highlights: { label: withNullAsUndefined(matchesFuzzy(filter, label, true)) }, highlights: { label: withNullAsUndefined(matchesFuzzy(filter, label, true)) },
accept: () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) accept: () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString())
@@ -5,7 +5,6 @@
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources'; import * as resources from 'vs/base/common/resources';
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform'; import * as platform from 'vs/base/common/platform';
import severity from 'vs/base/common/severity'; import severity from 'vs/base/common/severity';
import { Event, Emitter } from 'vs/base/common/event'; 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 { distinct } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { localize } from 'vs/nls';
export class DebugSession implements IDebugSession { export class DebugSession implements IDebugSession {
@@ -232,7 +232,7 @@ export class DebugSession implements IDebugSession {
*/ */
async launchOrAttach(config: IConfig): Promise<void> { async launchOrAttach(config: IConfig): Promise<void> {
if (!this.raw) { 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...) // __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<void> { async terminate(restart = false): Promise<void> {
if (!this.raw) { if (!this.raw) {
throw new Error('no debug adapter'); throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminate'));
} }
this.cancelAllRequests(); this.cancelAllRequests();
@@ -266,7 +266,7 @@ export class DebugSession implements IDebugSession {
*/ */
async disconnect(restart = false): Promise<void> { async disconnect(restart = false): Promise<void> {
if (!this.raw) { if (!this.raw) {
throw new Error('no debug adapter'); throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'disconnect'));
} }
this.cancelAllRequests(); this.cancelAllRequests();
@@ -278,7 +278,7 @@ export class DebugSession implements IDebugSession {
*/ */
async restart(): Promise<void> { async restart(): Promise<void> {
if (!this.raw) { if (!this.raw) {
throw new Error('no debug adapter'); throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restart'));
} }
this.cancelAllRequests(); this.cancelAllRequests();
@@ -287,7 +287,7 @@ export class DebugSession implements IDebugSession {
async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> { async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {
if (!this.raw) { 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) { if (!this.raw.readyForBreakpoints) {
@@ -321,7 +321,7 @@ export class DebugSession implements IDebugSession {
async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> { async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {
if (!this.raw) { 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) { if (this.raw.readyForBreakpoints) {
@@ -338,7 +338,7 @@ export class DebugSession implements IDebugSession {
async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> { async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {
if (!this.raw) { 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) { 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 }> { async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> {
if (!this.raw) { 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) { 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 }); const response = await this.raw.dataBreakpointInfo({ name, variablesReference });
@@ -360,7 +360,7 @@ export class DebugSession implements IDebugSession {
async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> { async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> {
if (!this.raw) { 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) { if (this.raw.readyForBreakpoints) {
@@ -377,7 +377,7 @@ export class DebugSession implements IDebugSession {
async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> { async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> {
if (!this.raw) { 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); const source = this.getRawSource(uri);
@@ -393,7 +393,7 @@ export class DebugSession implements IDebugSession {
customRequest(request: string, args: any): Promise<DebugProtocol.Response> { customRequest(request: string, args: any): Promise<DebugProtocol.Response> {
if (!this.raw) { 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); return this.raw.custom(request, args);
@@ -401,7 +401,7 @@ export class DebugSession implements IDebugSession {
stackTrace(threadId: number, startFrame: number, levels: number): Promise<DebugProtocol.StackTraceResponse> { stackTrace(threadId: number, startFrame: number, levels: number): Promise<DebugProtocol.StackTraceResponse> {
if (!this.raw) { 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); const token = this.getNewCancellationToken(threadId);
@@ -410,7 +410,7 @@ export class DebugSession implements IDebugSession {
async exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> { async exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {
if (!this.raw) { 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 }); const response = await this.raw.exceptionInfo({ threadId });
@@ -428,7 +428,7 @@ export class DebugSession implements IDebugSession {
scopes(frameId: number, threadId: number): Promise<DebugProtocol.ScopesResponse> { scopes(frameId: number, threadId: number): Promise<DebugProtocol.ScopesResponse> {
if (!this.raw) { 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); 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<DebugProtocol.VariablesResponse> { variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse> {
if (!this.raw) { 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; const token = threadId ? this.getNewCancellationToken(threadId) : undefined;
@@ -446,7 +446,7 @@ export class DebugSession implements IDebugSession {
evaluate(expression: string, frameId: number, context?: string): Promise<DebugProtocol.EvaluateResponse> { evaluate(expression: string, frameId: number, context?: string): Promise<DebugProtocol.EvaluateResponse> {
if (!this.raw) { 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 }); return this.raw.evaluate({ expression, frameId, context });
@@ -454,7 +454,7 @@ export class DebugSession implements IDebugSession {
async restartFrame(frameId: number, threadId: number): Promise<void> { async restartFrame(frameId: number, threadId: number): Promise<void> {
if (!this.raw) { 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); await this.raw.restartFrame({ frameId }, threadId);
@@ -462,7 +462,7 @@ export class DebugSession implements IDebugSession {
async next(threadId: number): Promise<void> { async next(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.next({ threadId });
@@ -470,7 +470,7 @@ export class DebugSession implements IDebugSession {
async stepIn(threadId: number): Promise<void> { async stepIn(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.stepIn({ threadId });
@@ -478,7 +478,7 @@ export class DebugSession implements IDebugSession {
async stepOut(threadId: number): Promise<void> { async stepOut(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.stepOut({ threadId });
@@ -486,7 +486,7 @@ export class DebugSession implements IDebugSession {
async stepBack(threadId: number): Promise<void> { async stepBack(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.stepBack({ threadId });
@@ -494,7 +494,7 @@ export class DebugSession implements IDebugSession {
async continue(threadId: number): Promise<void> { async continue(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.continue({ threadId });
@@ -502,7 +502,7 @@ export class DebugSession implements IDebugSession {
async reverseContinue(threadId: number): Promise<void> { async reverseContinue(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.reverseContinue({ threadId });
@@ -510,7 +510,7 @@ export class DebugSession implements IDebugSession {
async pause(threadId: number): Promise<void> { async pause(threadId: number): Promise<void> {
if (!this.raw) { 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 }); await this.raw.pause({ threadId });
@@ -518,7 +518,7 @@ export class DebugSession implements IDebugSession {
async terminateThreads(threadIds?: number[]): Promise<void> { async terminateThreads(threadIds?: number[]): Promise<void> {
if (!this.raw) { 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 }); await this.raw.terminateThreads({ threadIds });
@@ -526,7 +526,7 @@ export class DebugSession implements IDebugSession {
setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse> { setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse> {
if (!this.raw) { 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 }); 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<DebugProtocol.GotoTargetsResponse> { gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise<DebugProtocol.GotoTargetsResponse> {
if (!this.raw) { 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 }); return this.raw.gotoTargets({ source, line, column });
@@ -542,7 +542,7 @@ export class DebugSession implements IDebugSession {
goto(threadId: number, targetId: number): Promise<DebugProtocol.GotoResponse> { goto(threadId: number, targetId: number): Promise<DebugProtocol.GotoResponse> {
if (!this.raw) { 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 }); return this.raw.goto({ threadId, targetId });
@@ -550,7 +550,7 @@ export class DebugSession implements IDebugSession {
loadSource(resource: URI): Promise<DebugProtocol.SourceResponse> { loadSource(resource: URI): Promise<DebugProtocol.SourceResponse> {
if (!this.raw) { 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); const source = this.getSourceForUri(resource);
@@ -568,7 +568,7 @@ export class DebugSession implements IDebugSession {
async getLoadedSources(): Promise<Source[]> { async getLoadedSources(): Promise<Source[]> {
if (!this.raw) { 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({}); 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<DebugProtocol.CompletionsResponse> { async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<DebugProtocol.CompletionsResponse> {
if (!this.raw) { 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({ return this.raw.completions({
@@ -700,7 +700,7 @@ export class DebugSession implements IDebugSession {
} }
this.rawListeners.push(this.raw.onDidInitialize(async () => { this.rawListeners.push(this.raw.onDidInitialize(async () => {
aria.status(nls.localize('debuggingStarted', "Debugging started.")); aria.status(localize('debuggingStarted', "Debugging started."));
const sendConfigurationDone = async () => { const sendConfigurationDone = async () => {
if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {
try { try {
@@ -782,7 +782,7 @@ export class DebugSession implements IDebugSession {
})); }));
this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => { 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) { if (event.body && event.body.restart) {
await this.debugService.restartSession(this, event.body.restart); await this.debugService.restartSession(this, event.body.restart);
} else if (this.raw) { } else if (this.raw) {
@@ -230,7 +230,7 @@ export class RawDebugSession implements IDisposable {
*/ */
async start(): Promise<void> { async start(): Promise<void> {
if (!this.debugAdapter) { 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(); await this.debugAdapter.startSession();
@@ -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 // Produce a well formed file name
name = getWellFormedFileName(name); name = getWellFormedFileName(name);
// Name not provided // Name not provided
if (!name || name.length === 0 || /^\s+$/.test(name)) { 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 // Relative paths only
if (name[0] === '/' || name[0] === '\\') { 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(/[\\/]/)); 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 // Do not allow to overwrite existing file
const child = parent?.getChild(name); const child = parent?.getChild(name);
if (child && child !== item) { 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 // Invalid File name
const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows; const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows;
if (names.some((folderName) => !extpath.isValidBasename(folderName, windowsBasenameValidity))) { 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; return null;
@@ -804,7 +823,7 @@ export function getWellFormedFileName(filename: string): string {
// Trim tabs // Trim tabs
filename = strings.trim(filename, '\t'); 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, '/'); filename = strings.rtrim(filename, '/');
filename = strings.rtrim(filename, '\\'); filename = strings.rtrim(filename, '\\');
@@ -376,13 +376,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
const inputBox = new InputBox(label.element, this.contextViewService, { const inputBox = new InputBox(label.element, this.contextViewService, {
validationOptions: { validationOptions: {
validation: (value) => { validation: (value) => {
const content = editableData.validationMessage(value); const message = editableData.validationMessage(value);
if (!content) { if (!message || message.severity !== Severity.Error) {
return null; return null;
} }
return { return {
content, content: message.content,
formatContent: true, formatContent: true,
type: MessageType.ERROR type: MessageType.ERROR
}; };
@@ -392,10 +392,6 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
}); });
const styler = attachInputBoxStyler(inputBox, this.themeService); const styler = attachInputBoxStyler(inputBox, this.themeService);
inputBox.onDidChange(value => {
label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing!
});
const lastDot = value.lastIndexOf('.'); const lastDot = value.lastIndexOf('.');
inputBox.value = value; inputBox.value = value;
@@ -412,8 +408,27 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
} }
}); });
const showInputBoxNotification = () => {
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 = [ const toDispose = [
inputBox, 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) => { DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => {
if (e.equals(KeyCode.Enter)) { if (e.equals(KeyCode.Enter)) {
if (inputBox.validate()) { if (inputBox.validate()) {
@@ -423,6 +438,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
done(false, true); done(false, true);
} }
}), }),
DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_UP, (e: IKeyboardEvent) => {
showInputBoxNotification();
}),
DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => {
done(inputBox.isInputValid(), true); done(inputBox.isInputValid(), true);
}), }),
@@ -95,6 +95,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuic
if (this.contextKeyService.contextMatchesRules(view.when)) { if (this.contextKeyService.contextMatchesRules(view.when)) {
result.push({ result.push({
label: view.name, label: view.name,
ariaLabel: localize('viewPickAriaLabel', "{0}, view picker", view.name),
containerLabel: viewlet.name, containerLabel: viewlet.name,
accept: () => this.viewsService.openView(view.id, true) accept: () => this.viewsService.openView(view.id, true)
}); });
@@ -110,6 +111,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuic
if (this.includeViewlet(viewlet)) { if (this.includeViewlet(viewlet)) {
viewEntries.push({ viewEntries.push({
label: viewlet.name, label: viewlet.name,
ariaLabel: localize('viewPickAriaLabel', "{0}, view picker", viewlet.name),
containerLabel: localize('views', "Side Bar"), containerLabel: localize('views', "Side Bar"),
accept: () => this.viewletService.openViewlet(viewlet.id, true) accept: () => this.viewletService.openViewlet(viewlet.id, true)
}); });
@@ -121,6 +123,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuic
for (const panel of panels) { for (const panel of panels) {
viewEntries.push({ viewEntries.push({
label: panel.name, label: panel.name,
ariaLabel: localize('viewPickAriaLabel', "{0}, view picker", panel.name),
containerLabel: localize('panels', "Panel"), containerLabel: localize('panels', "Panel"),
accept: () => this.panelService.openPanel(panel.id, true) accept: () => this.panelService.openPanel(panel.id, true)
}); });
@@ -137,8 +140,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuic
// Terminals // Terminals
this.terminalService.terminalTabs.forEach((tab, tabIndex) => { this.terminalService.terminalTabs.forEach((tab, tabIndex) => {
tab.terminalInstances.forEach((terminal, terminalIndex) => { tab.terminalInstances.forEach((terminal, terminalIndex) => {
const label = localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title);
viewEntries.push({ 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"), containerLabel: localize('terminals', "Terminal"),
accept: async () => { accept: async () => {
await this.terminalService.showPanel(true); await this.terminalService.showPanel(true);
@@ -152,8 +157,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuic
// Output Channels // Output Channels
const channels = this.outputService.getChannelDescriptors(); const channels = this.outputService.getChannelDescriptors();
for (const channel of channels) { for (const channel of channels) {
const label = channel.log ? localize('logChannel', "Log ({0})", channel.label) : channel.label;
viewEntries.push({ viewEntries.push({
label: channel.log ? localize('logChannel', "Log ({0})", channel.label) : channel.label, label,
ariaLabel: localize('viewPickAriaLabel', "{0}, view picker", label),
containerLabel: localize('channels', "Output"), containerLabel: localize('channels', "Output"),
accept: () => this.outputService.showChannel(channel.id) accept: () => this.outputService.showChannel(channel.id)
}); });
@@ -28,7 +28,7 @@ import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/pl
import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; 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 { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; 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 { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { once } from 'vs/base/common/functional'; import { once } from 'vs/base/common/functional';
@@ -282,13 +282,13 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
ariaLabel: nls.localize('remote.tunnelsView.input', "Press Enter to confirm or Escape to cancel."), ariaLabel: nls.localize('remote.tunnelsView.input', "Press Enter to confirm or Escape to cancel."),
validationOptions: { validationOptions: {
validation: (value) => { validation: (value) => {
const content = editableData.validationMessage(value); const message = editableData.validationMessage(value);
if (!content) { if (!message || message.severity !== Severity.Error) {
return null; return null;
} }
return { return {
content, content: message.content,
formatContent: true, formatContent: true,
type: MessageType.ERROR 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 { namespace LabelTunnelAction {
export const ID = 'remote.tunnel.label'; export const ID = 'remote.tunnel.label';
export const LABEL = nls.localize('remote.tunnel.label', "Set Label"); export const LABEL = nls.localize('remote.tunnel.label', "Set Label");
@@ -733,7 +744,7 @@ namespace ForwardPortAction {
} }
remoteExplorerService.setEditable(undefined, null); remoteExplorerService.setEditable(undefined, null);
}, },
validationMessage: validateInput, validationMessage: (value) => validationMessage(validateInput(value)),
placeholder: forwardPrompt placeholder: forwardPrompt
}); });
} }
@@ -916,7 +927,7 @@ namespace ChangeLocalPortAction {
} }
} }
}, },
validationMessage: validateInput, validationMessage: (value) => validationMessage(validateInput(value)),
placeholder: nls.localize('remote.tunnelsView.changePort', "New local port") placeholder: nls.localize('remote.tunnelsView.changePort', "New local port")
}); });
} }
@@ -5,7 +5,7 @@
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; 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 { matchesFuzzy } from 'vs/base/common/filters';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
@@ -59,14 +59,12 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
taskMap[key] = task; taskMap[key] = task;
} }
} }
for (const key of recentlyUsedTasks.keys()) {
recentlyUsedTasks.keys().forEach(key => {
const task = taskMap[key]; const task = taskMap[key];
if (task) { if (task) {
recent.push(task); recent.push(task);
} }
}); }
for (const task of tasks) { for (const task of tasks) {
const key = task.getRecentlyUsedKey(); const key = task.getRecentlyUsedKey();
if (!key || !recentlyUsedTasks.has(key)) { if (!key || !recentlyUsedTasks.has(key)) {
@@ -83,13 +81,13 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
// Fill picks in sorted order // Fill picks in sorted order
this.fillPicks(taskPicks, filter, recent, localize('recentlyUsed', 'recently used tasks')); this.fillPicks(taskPicks, filter, recent, localize('recentlyUsed', "recently used tasks"));
configured.sort((a, b) => sorter.compare(a, b)); configured.sort((a, b) => 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)); 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; return taskPicks;
} }
@@ -107,7 +105,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
} }
taskPicks.push({ taskPicks.push({
label: task._label, label: task._label,
ariaLabel: localize('entryAriaLabel', "{0}, tasks", task._label), ariaLabel: localize('entryAriaLabel', "{0}, tasks picker", task._label),
description: this.taskService.getTaskDescription(task), description: this.taskService.getTaskDescription(task),
highlights: { label: highlights }, highlights: { label: highlights },
buttons: (() => { buttons: (() => {
@@ -122,9 +120,6 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
return buttons; return buttons;
})(), })(),
accept: () => {
this.taskService.run(task, { attachProblemMatcher: true });
},
trigger: () => { trigger: () => {
if (ContributedTask.is(task)) { if (ContributedTask.is(task)) {
this.taskService.customize(task, undefined, true); this.taskService.customize(task, undefined, true);
@@ -132,7 +127,10 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
this.taskService.openConfig(task); this.taskService.openConfig(task);
} }
return true; // close picker return TriggerAction.CLOSE_PICKER;
},
accept: () => {
this.taskService.run(task, { attachProblemMatcher: true });
} }
}); });
} }
@@ -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<IPickerQuickAccessItem> {
static PREFIX = 'term ';
constructor(
@ITerminalService private readonly terminalService: ITerminalService,
@ICommandService private readonly commandService: ICommandService,
) {
super(TerminalQuickAccessProvider.PREFIX);
}
protected getPicks(filter: string): Array<IPickerQuickAccessItem | IQuickPickSeparator> {
const terminalPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
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;
}
}
@@ -39,6 +39,8 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal
import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; 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); registerSingleton(ITerminalService, TerminalService, true);
@@ -60,6 +62,16 @@ quickOpenRegistry.registerQuickOpenHandler(
) )
); );
const quickAccessRegistry = (Registry.as<IQuickAccessRegistry>(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'; const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker';
CommandsRegistry.registerCommand( CommandsRegistry.registerCommand(
{ id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) }); { id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) });
@@ -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 { 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 { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; 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 { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IViewDescriptorService } from 'vs/workbench/common/views';
import { basename } from 'vs/base/common/path'; 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 { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction, ActionRunner } from 'vs/base/common/actions'; import { IAction, ActionRunner } from 'vs/base/common/actions';
import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; 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 { fromNow } from 'vs/base/common/date';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { escapeRegExpCharacters } from 'vs/base/common/strings';
@@ -403,7 +403,7 @@ export class TimelinePane extends ViewPane {
private async handleRequest(request: TimelineRequest) { private async handleRequest(request: TimelineRequest) {
let timeline: Timeline | undefined; let timeline: Timeline | undefined;
try { try {
timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result); timeline = await this.progressService.withProgress({ location: this.id }, () => request.result);
} }
finally { finally {
this._pendingRequests.delete(request.source); this._pendingRequests.delete(request.source);
@@ -932,38 +932,35 @@ class TimelinePaneCommands extends Disposable {
} }
})); }));
this._register(registerAction2(class extends Action2 { this._register(CommandsRegistry.registerCommand('timeline.toggleFollowActiveEditor',
constructor() { (accessor: ServicesAccessor, ...args: any[]) => pane.followActiveEditor = !pane.followActiveEditor
super({ ));
id: 'timeline.toggleFollowActiveEditor',
title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, command: {
menu: [{ id: 'timeline.toggleFollowActiveEditor',
id: MenuId.TimelineTitle, title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' },
command: { // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"),
// title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), icon: { id: 'codicon/eye' },
icon: { id: 'codicon/eye' } category: { value: localize('timeline', "Timeline"), original: 'Timeline' },
}, },
group: 'navigation', group: 'navigation',
order: 98, order: 98,
when: TimelineFollowActiveEditorContext when: TimelineFollowActiveEditorContext
}, })));
{
id: MenuId.TimelineTitle, this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
command: { command: {
// title: localize(`ToggleFollowActiveEditorCommand.follow`, "Follow the Active Editor"), id: 'timeline.toggleFollowActiveEditor',
icon: { id: 'codicon/eye-closed' } title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' },
}, // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"),
group: 'navigation', icon: { id: 'codicon/eye-closed' },
order: 98, category: { value: localize('timeline', "Timeline"), original: 'Timeline' },
when: TimelineFollowActiveEditorContext.toNegated() },
}] group: 'navigation',
}); order: 98,
} when: TimelineFollowActiveEditorContext.toNegated()
run(accessor: ServicesAccessor, ...args: any[]) { })));
pane.followActiveEditor = !pane.followActiveEditor;
}
}));
this._register(timelineService.onDidChangeProviders(() => this.updateTimelineSourceFilters())); this._register(timelineService.onDidChangeProviders(() => this.updateTimelineSourceFilters()));
this.updateTimelineSourceFilters(); this.updateTimelineSourceFilters();
@@ -30,7 +30,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; 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 { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
@@ -60,14 +60,14 @@ const enum AuthStatus {
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing); const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', ''); const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('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) { switch (source) {
case SyncSource.Settings: return localize('settings', "Settings"); case SyncResource.Settings: return localize('settings', "Settings");
case SyncSource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); case SyncResource.Keybindings: return localize('keybindings', "Keyboard Shortcuts");
case SyncSource.Extensions: return localize('extensions', "Extensions"); case SyncResource.Extensions: return localize('extensions', "Extensions");
case SyncSource.GlobalState: return localize('ui state label', "UI State"); case SyncResource.GlobalState: return localize('ui state label', "UI State");
} }
} }
@@ -283,8 +283,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
this.updateBadge(); this.updateBadge();
} }
private readonly conflictsDisposables = new Map<SyncSource, IDisposable>(); private readonly conflictsDisposables = new Map<SyncResource, IDisposable>();
private onDidChangeConflicts(conflicts: SyncSource[]) { private onDidChangeConflicts(conflicts: SyncResource[]) {
this.updateBadge(); this.updateBadge();
if (conflicts.length) { if (conflicts.length) {
this.conflictsSources.set(this.userDataSyncService.conflictsSources.join(',')); 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 { 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) { if (contents) {
await this.userDataSyncService.accept(syncSource, contents); await this.userDataSyncService.accept(syncResource, contents);
} }
} catch (e) { } catch (e) {
this.notificationService.error(e); this.notificationService.error(e);
} }
} }
private async acceptLocal(syncSource: SyncSource): Promise<void> { private async acceptLocal(syncSource: SyncResource): Promise<void> {
try { try {
const previewResource = syncSource === SyncSource.Settings const previewResource = syncSource === SyncResource.Settings
? this.workbenchEnvironmentService.settingsSyncPreviewResource ? this.workbenchEnvironmentService.settingsSyncPreviewResource
: syncSource === SyncSource.Keybindings : syncSource === SyncResource.Keybindings
? this.workbenchEnvironmentService.keybindingsSyncPreviewResource ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource
: null; : null;
if (previewResource) { if (previewResource) {
@@ -415,15 +415,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}); });
return; return;
case UserDataSyncErrorCode.TooLarge: case UserDataSyncErrorCode.TooLarge:
if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) {
this.disableSync(error.source); this.disableSync(error.resource);
const sourceArea = getSyncAreaLabel(error.source); const sourceArea = getSyncAreaLabel(error.resource);
this.notificationService.notify({ this.notificationService.notify({
severity: Severity.Error, 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'), 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: { actions: {
primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true, 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<SyncSource, IDisposable>(); private readonly invalidContentErrorDisposables = new Map<SyncResource, IDisposable>();
private onSyncErrors(errors: [SyncSource, UserDataSyncError][]): void { private onSyncErrors(errors: [SyncResource, UserDataSyncError][]): void {
if (errors.length) { if (errors.length) {
for (const [source, error] of errors) { for (const [source, error] of errors) {
switch (error.code) { 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)) { if (this.invalidContentErrorDisposables.has(source)) {
return; return;
} }
if (source !== SyncSource.Settings && source !== SyncSource.Keybindings) { if (source !== SyncResource.Settings && source !== SyncResource.Keybindings) {
return; 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 }))) { if (isEqual(resource, toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }))) {
// Do not show notification if the file in error is active // Do not show notification if the file in error is active
return; 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()), 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: { actions: {
primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true, 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(() => { this.invalidContentErrorDisposables.set(source, toDisposable(() => {
@@ -602,17 +602,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
return [{ return [{
id: 'settings', id: SyncResource.Settings,
label: getSyncAreaLabel(SyncSource.Settings) label: getSyncAreaLabel(SyncResource.Settings)
}, { }, {
id: 'keybindings', id: SyncResource.Keybindings,
label: getSyncAreaLabel(SyncSource.Keybindings) label: getSyncAreaLabel(SyncResource.Keybindings)
}, { }, {
id: 'extensions', id: SyncResource.Extensions,
label: getSyncAreaLabel(SyncSource.Extensions) label: getSyncAreaLabel(SyncResource.Extensions)
}, { }, {
id: 'globalState', id: SyncResource.GlobalState,
label: getSyncAreaLabel(SyncSource.GlobalState), label: getSyncAreaLabel(SyncResource.GlobalState),
description: localize('ui state description', "only 'Display Language' for now") 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) { if (source === undefined) {
this.userDataSyncEnablementService.setEnablement(false); this.userDataSyncEnablementService.setEnablement(false);
} else { } else {
switch (source) { switch (source) {
case SyncSource.Settings: return this.userDataSyncEnablementService.setResourceEnablement('settings', false); case SyncResource.Settings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Settings, false);
case SyncSource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement('keybindings', false); case SyncResource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Keybindings, false);
case SyncSource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement('extensions', false); case SyncResource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Extensions, false);
case SyncSource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement('globalState', 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 { private getConflictsEditorInput(source: SyncResource): IEditorInput | undefined {
const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource const previewResource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource
: source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource : source === SyncResource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource
: null; : null;
return previewResource ? this.editorService.editors.filter(input => input instanceof DiffEditorInput && isEqual(previewResource, input.master.resource))[0] : undefined; 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<void> { private async handleConflicts(resource: SyncResource): Promise<void> {
let previewResource: URI | undefined = undefined; let previewResource: URI | undefined = undefined;
let label: string = ''; let label: string = '';
if (source === SyncSource.Settings) { if (resource === SyncResource.Settings) {
previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource; previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource;
label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)");
} else if (source === SyncSource.Keybindings) { } else if (resource === SyncResource.Keybindings) {
previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource; previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource;
label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)");
} }
if (previewResource) { if (previewResource) {
const remoteContentResource = toRemoteSyncResourceFromSource(source).with({ query: PREVIEW_QUERY }); const remoteContentResource = toRemoteSyncResource(resource).with({ query: PREVIEW_QUERY });
await this.editorService.openEditor({ await this.editorService.openEditor({
leftResource: remoteContentResource, leftResource: remoteContentResource,
rightResource: previewResource, rightResource: previewResource,
@@ -846,7 +846,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private registerShowSettingsConflictsAction(): void { private registerShowSettingsConflictsAction(): void {
const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); 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, { MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync', group: '5_sync',
command: { command: {
@@ -873,7 +873,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private registerShowKeybindingsConflictsAction(): void { private registerShowKeybindingsConflictsAction(): void {
const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); 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, { MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync', group: '5_sync',
command: { command: {
@@ -934,10 +934,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
if (that.userDataSyncService.conflictsSources.length) { if (that.userDataSyncService.conflictsSources.length) {
for (const source of that.userDataSyncService.conflictsSources) { for (const source of that.userDataSyncService.conflictsSources) {
switch (source) { switch (source) {
case SyncSource.Settings: case SyncResource.Settings:
items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title }); items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title });
break; break;
case SyncSource.Keybindings: case SyncResource.Keybindings:
items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title }); items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title });
break; break;
} }
@@ -1130,7 +1130,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
this._register(this.acceptChangesButton.onClick(async () => { this._register(this.acceptChangesButton.onClick(async () => {
const model = this.editor.getModel(); const model = this.editor.getModel();
if (model) { 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' }); this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: isRemote ? 'acceptRemote' : 'acceptLocal' });
const syncAreaLabel = getSyncAreaLabel(conflictsSource); const syncAreaLabel = getSyncAreaLabel(conflictsSource);
const result = await this.dialogService.confirm({ const result = await this.dialogService.confirm({
@@ -10,7 +10,7 @@ import { localize } from 'vs/nls';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView'; import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; 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 { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
@@ -61,8 +61,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
if (visible && !treeView.dataProvider) { if (visible && !treeView.dataProvider) {
disposable.dispose(); disposable.dispose();
treeView.dataProvider = this.instantiationService.createInstance(UserDataSyncHistoryViewDataProvider, id, treeView.dataProvider = this.instantiationService.createInstance(UserDataSyncHistoryViewDataProvider, id,
(resourceKey: ResourceKey) => remote ? this.userDataSyncStoreService.getAllRefs(resourceKey) : this.userDataSyncBackupStoreService.getAllRefs(resourceKey), (resource: SyncResource) => remote ? this.userDataSyncStoreService.getAllRefs(resource) : this.userDataSyncBackupStoreService.getAllRefs(resource),
(resourceKey: ResourceKey, ref: string) => remote ? toRemoteSyncResource(resourceKey, ref) : toLocalBackupSyncResource(resourceKey, ref)); (resource: SyncResource, ref: string) => remote ? toRemoteSyncResource(resource, ref) : toLocalBackupSyncResource(resource, ref));
} }
}); });
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry); const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
@@ -114,7 +114,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
let resource = URI.parse(handle.$treeItemHandle); let resource = URI.parse(handle.$treeItemHandle);
const result = resolveSyncResource(resource); const result = resolveSyncResource(resource);
if (result) { if (result) {
resource = resource.with({ fragment: result.resourceKey }); resource = resource.with({ fragment: result.resource });
await editorService.openEditor({ resource }); await editorService.openEditor({ resource });
} }
} }
@@ -152,8 +152,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
const resource = URI.parse(handle.$treeItemHandle); const resource = URI.parse(handle.$treeItemHandle);
const result = resolveSyncResource(resource); const result = resolveSyncResource(resource);
if (result) { if (result) {
const leftResource: URI = resource.with({ fragment: result.resourceKey }); const leftResource: URI = resource.with({ fragment: result.resource });
const rightResource: URI = result.resourceKey === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource; const rightResource: URI = result.resource === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource;
await editorService.openEditor({ await editorService.openEditor({
leftResource, leftResource,
rightResource, rightResource,
@@ -174,8 +174,8 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
constructor( constructor(
private readonly viewId: string, private readonly viewId: string,
private getAllRefs: (resourceKey: ResourceKey) => Promise<IResourceRefHandle[]>, private getAllRefs: (resource: SyncResource) => Promise<IResourceRefHandle[]>,
private toResource: (resourceKey: ResourceKey, ref: string) => URI private toResource: (resource: SyncResource, ref: string) => URI
) { ) {
} }
@@ -183,7 +183,7 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
if (element) { if (element) {
return this.getResources(element.handle); return this.getResources(element.handle);
} }
return ALL_RESOURCE_KEYS.map(resourceKey => ({ return ALL_SYNC_RESOURCES.map(resourceKey => ({
handle: resourceKey, handle: resourceKey,
collapsibleState: TreeItemCollapsibleState.Collapsed, collapsibleState: TreeItemCollapsibleState.Collapsed,
label: { label: resourceKey }, label: { label: resourceKey },
@@ -193,7 +193,7 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
} }
private async getResources(handle: string): Promise<ITreeItem[]> { private async getResources(handle: string): Promise<ITreeItem[]> {
const resourceKey = ALL_RESOURCE_KEYS.filter(key => key === handle)[0]; const resourceKey = ALL_SYNC_RESOURCES.filter(key => key === handle)[0];
if (resourceKey) { if (resourceKey) {
const refHandles = await this.getAllRefs(resourceKey); const refHandles = await this.getAllRefs(resourceKey);
return refHandles.map(({ ref, created }) => { return refHandles.map(({ ref, created }) => {
@@ -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); const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined);
modelPromise = model.load(options); modelPromise = model.load(options);
// Install model listeners this.registerModel(newModel);
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);
} }
// Store pending loads to avoid race conditions // 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) // Make known to manager (if not already known)
this.add(resource, model); this.add(resource, model);
// Signal as event if we created the model // Emit some events if we created the model
if (didCreateModel) { if (didCreateModel) {
this._onDidCreate.fire(model); 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 { 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 { add(resource: URI, model: TextFileEditorModel): void {
const knownModel = this.mapResourceToModel.get(resource); const knownModel = this.mapResourceToModel.get(resource);
if (knownModel === model) { if (knownModel === model) {
@@ -219,11 +219,13 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
} }
private registerModel(model: UntitledTextEditorModel): void { private registerModel(model: UntitledTextEditorModel): void {
const modelDisposables = new DisposableStore();
modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); // Install model listeners
modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); const modelListeners = new DisposableStore();
modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model)));
modelDisposables.add(model.onDispose(() => this._onDidDispose.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 // Remove from cache on dispose
Event.once(model.onDispose)(() => { Event.once(model.onDispose)(() => {
@@ -232,11 +234,17 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
this.mapResourceToModel.delete(model.resource); this.mapResourceToModel.delete(model.resource);
// Listeners // Listeners
modelDisposables.dispose(); modelListeners.dispose();
}); });
// Add to cache // Add to cache
this.mapResourceToModel.set(model.resource, model); 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);
}
} }
} }
@@ -14,6 +14,7 @@ import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRe
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
suite('Untitled text editors', () => { suite('Untitled text editors', () => {
@@ -120,15 +121,23 @@ suite('Untitled text editors', () => {
const service = accessor.untitledTextEditorService; const service = accessor.untitledTextEditorService;
const file = URI.file(join('C:\\', '/foo/file.txt')); 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.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); assert.equal(untitled.isDirty(), true);
untitled.dispose(); untitled.dispose();
listener.dispose();
}); });
test('no longer dirty when content gets empty (not with associated resource)', async () => { test('no longer dirty when content gets empty (not with associated resource)', async () => {
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
@@ -17,7 +17,7 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ
private readonly channel: IChannel; private readonly channel: IChannel;
readonly resourceKey = 'settings'; readonly resourceKey = 'settings';
readonly source = SyncSource.Settings; readonly resource = SyncResource.Settings;
private _status: SyncStatus = SyncStatus.Uninitialized; private _status: SyncStatus = SyncStatus.Uninitialized;
get status(): SyncStatus { return this._status; } get status(): SyncStatus { return this._status; }
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -19,16 +19,16 @@ export class UserDataSyncBackupStoreService implements IUserDataSyncBackupStoreS
this.channel = sharedProcessService.getChannel('userDataSyncBackupStoreService'); this.channel = sharedProcessService.getChannel('userDataSyncBackupStoreService');
} }
backup(key: ResourceKey, content: string): Promise<void> { backup(key: SyncResource, content: string): Promise<void> {
return this.channel.call('backup', [key, content]); return this.channel.call('backup', [key, content]);
} }
getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]> { getAllRefs(key: SyncResource): Promise<IResourceRefHandle[]> {
return this.channel.call('getAllRefs', [key]); return this.channel.call('getAllRefs', [key]);
} }
resolveContent(key: ResourceKey, ref: string): Promise<string | null> { resolveContent(key: SyncResource, ref: string): Promise<string | null> {
return this.channel.call('resolveContent', [key, ref]); return this.channel.call('resolveContent', [key, ref]);
} }
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
@@ -23,20 +23,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>()); private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event; readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
get onDidChangeLocal(): Event<SyncSource> { return this.channel.listen<SyncSource>('onDidChangeLocal'); } get onDidChangeLocal(): Event<SyncResource> { return this.channel.listen<SyncResource>('onDidChangeLocal'); }
private _conflictsSources: SyncSource[] = []; private _conflictsSources: SyncResource[] = [];
get conflictsSources(): SyncSource[] { return this._conflictsSources; } get conflictsSources(): SyncResource[] { return this._conflictsSources; }
private _onDidChangeConflicts: Emitter<SyncSource[]> = this._register(new Emitter<SyncSource[]>()); private _onDidChangeConflicts: Emitter<SyncResource[]> = this._register(new Emitter<SyncResource[]>());
readonly onDidChangeConflicts: Event<SyncSource[]> = this._onDidChangeConflicts.event; readonly onDidChangeConflicts: Event<SyncResource[]> = this._onDidChangeConflicts.event;
private _lastSyncTime: number | undefined = undefined; private _lastSyncTime: number | undefined = undefined;
get lastSyncTime(): number | undefined { return this._lastSyncTime; } get lastSyncTime(): number | undefined { return this._lastSyncTime; }
private _onDidChangeLastSyncTime: Emitter<number> = this._register(new Emitter<number>()); private _onDidChangeLastSyncTime: Emitter<number> = this._register(new Emitter<number>());
readonly onDidChangeLastSyncTime: Event<number> = this._onDidChangeLastSyncTime.event; readonly onDidChangeLastSyncTime: Event<number> = this._onDidChangeLastSyncTime.event;
private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event;
constructor( constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService @ISharedProcessService sharedProcessService: ISharedProcessService
@@ -52,7 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return userDataSyncChannel.listen(event, arg); 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.updateStatus(status);
this.updateConflicts(conflicts); this.updateConflicts(conflicts);
if (lastSyncTime) { if (lastSyncTime) {
@@ -61,8 +61,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status))); this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
this._register(this.channel.listen<number>('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); this._register(this.channel.listen<number>('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime)));
}); });
this._register(this.channel.listen<SyncSource[]>('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); this._register(this.channel.listen<SyncResource[]>('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<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)])))));
} }
pull(): Promise<void> { pull(): Promise<void> {
@@ -73,7 +73,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('sync'); return this.channel.call('sync');
} }
accept(source: SyncSource, content: string): Promise<void> { accept(source: SyncResource, content: string): Promise<void> {
return this.channel.call('accept', [source, content]); return this.channel.call('accept', [source, content]);
} }
@@ -102,7 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this._onDidChangeStatus.fire(status); this._onDidChangeStatus.fire(status);
} }
private async updateConflicts(conflicts: SyncSource[]): Promise<void> { private async updateConflicts(conflicts: SyncResource[]): Promise<void> {
this._conflictsSources = conflicts; this._conflictsSources = conflicts;
this._onDidChangeConflicts.fire(conflicts); this._onDidChangeConflicts.fire(conflicts);
} }
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -25,11 +25,11 @@ export class UserDataSyncStoreService implements IUserDataSyncStoreService {
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
} }
read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise<IUserData> { read(key: SyncResource, oldValue: IUserData | null, source?: SyncResource): Promise<IUserData> {
throw new Error('Not Supported'); throw new Error('Not Supported');
} }
write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise<string> { write(key: SyncResource, content: string, ref: string | null, source?: SyncResource): Promise<string> {
throw new Error('Not Supported'); throw new Error('Not Supported');
} }
@@ -41,15 +41,15 @@ export class UserDataSyncStoreService implements IUserDataSyncStoreService {
throw new Error('Not Supported'); throw new Error('Not Supported');
} }
getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]> { getAllRefs(key: SyncResource): Promise<IResourceRefHandle[]> {
return this.channel.call('getAllRefs', [key]); return this.channel.call('getAllRefs', [key]);
} }
resolveContent(key: ResourceKey, ref: string): Promise<string | null> { resolveContent(key: SyncResource, ref: string): Promise<string | null> {
return this.channel.call('resolveContent', [key, ref]); return this.channel.call('resolveContent', [key, ref]);
} }
delete(key: ResourceKey): Promise<void> { delete(key: SyncResource): Promise<void> {
return this.channel.call('delete', [key]); return this.channel.call('delete', [key]);
} }