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