mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568
This commit is contained in:
@@ -53,6 +53,7 @@ export class AuthenticationTokenService extends Disposable implements IAuthentic
|
||||
}
|
||||
|
||||
sendTokenFailed(): void {
|
||||
this.setToken(undefined);
|
||||
this._onTokenFailed.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +328,11 @@ class ConfigurationRegistry implements IConfigurationRegistry {
|
||||
this.configurationProperties[key] = properties[key];
|
||||
}
|
||||
|
||||
if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {
|
||||
// If not set, default deprecationMessage to the markdown source
|
||||
properties[key].deprecationMessage = properties[key].markdownDeprecationMessage;
|
||||
}
|
||||
|
||||
propertyKeys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface ParsedArgs {
|
||||
log?: string;
|
||||
logExtensionHostCommunication?: boolean;
|
||||
'extensions-dir'?: string;
|
||||
'extensions-download-dir'?: string;
|
||||
'builtin-extensions-dir'?: string;
|
||||
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
|
||||
extensionTestsPath?: string; // either a local path or a URI
|
||||
@@ -54,7 +55,7 @@ export interface ParsedArgs {
|
||||
'locate-extension'?: string[]; // undefined or array of 1 or more
|
||||
'enable-proposed-api'?: string[]; // undefined or array of 1 or more
|
||||
'open-url'?: boolean;
|
||||
'skip-getting-started'?: boolean;
|
||||
'skip-release-notes'?: boolean;
|
||||
'disable-restore-windows'?: boolean;
|
||||
'disable-telemetry'?: boolean;
|
||||
'export-default-configuration'?: string;
|
||||
@@ -143,6 +144,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
|
||||
|
||||
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
'extensions-download-dir': { type: 'string' },
|
||||
'builtin-extensions-dir': { type: 'string' },
|
||||
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
||||
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
|
||||
@@ -179,7 +181,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'install-source': { type: 'string' },
|
||||
'driver': { type: 'string' },
|
||||
'logExtensionHostCommunication': { type: 'boolean' },
|
||||
'skip-getting-started': { type: 'boolean' },
|
||||
'skip-release-notes': { type: 'boolean' },
|
||||
'disable-restore-windows': { type: 'boolean' },
|
||||
'disable-telemetry': { type: 'boolean' },
|
||||
'disable-updates': { type: 'boolean' },
|
||||
@@ -265,23 +267,25 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
|
||||
const parsedArgs = minimist(args, { string, boolean, alias });
|
||||
|
||||
const cleanedArgs: any = {};
|
||||
const remainingArgs: any = parsedArgs;
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/58177
|
||||
cleanedArgs._ = parsedArgs._.filter(arg => arg.length > 0);
|
||||
delete parsedArgs._;
|
||||
|
||||
delete remainingArgs._;
|
||||
|
||||
for (let optionId in options) {
|
||||
const o = options[optionId];
|
||||
if (o.alias) {
|
||||
delete parsedArgs[o.alias];
|
||||
delete remainingArgs[o.alias];
|
||||
}
|
||||
|
||||
let val = parsedArgs[optionId];
|
||||
if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates)) {
|
||||
let val = remainingArgs[optionId];
|
||||
if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) {
|
||||
if (!val) {
|
||||
val = parsedArgs[o.deprecates];
|
||||
val = remainingArgs[o.deprecates];
|
||||
}
|
||||
delete parsedArgs[o.deprecates];
|
||||
delete remainingArgs[o.deprecates];
|
||||
}
|
||||
|
||||
if (typeof val !== 'undefined') {
|
||||
@@ -297,10 +301,10 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
|
||||
}
|
||||
cleanedArgs[optionId] = val;
|
||||
}
|
||||
delete parsedArgs[optionId];
|
||||
delete remainingArgs[optionId];
|
||||
}
|
||||
|
||||
for (let key in parsedArgs) {
|
||||
for (let key in remainingArgs) {
|
||||
errorReporter.onUnknownOption(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
||||
installSourcePath: string;
|
||||
|
||||
extensionsPath?: string;
|
||||
extensionsDownloadPath?: string;
|
||||
builtinExtensionsPath: string;
|
||||
|
||||
globalStorageHome: string;
|
||||
@@ -150,6 +151,10 @@ export class EnvironmentService implements INativeEnvironmentService {
|
||||
}
|
||||
}
|
||||
|
||||
get extensionsDownloadPath(): string | undefined {
|
||||
return parsePathArg(this._args['extensions-download-dir'], process);
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionsPath(): string {
|
||||
const fromArgs = parsePathArg(this._args['extensions-dir'], process);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { IRequestService, asJson, asText } from 'vs/platform/request/common/requ
|
||||
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}}
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -21,7 +20,6 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionManifest, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} add imports
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
@@ -704,9 +702,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
});
|
||||
}
|
||||
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<URI> {
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
|
||||
const zip = joinPath(location, generateUuid());
|
||||
const data = getGalleryExtensionTelemetryData(extension);
|
||||
const startTime = new Date().getTime();
|
||||
/* __GDPR__
|
||||
@@ -728,9 +725,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
} : extension.assets.download;
|
||||
|
||||
return this.getAsset(downloadAsset)
|
||||
.then(context => this.fileService.writeFile(zip, context.stream))
|
||||
.then(() => log(new Date().getTime() - startTime))
|
||||
.then(() => zip);
|
||||
.then(context => this.fileService.writeFile(location, context.stream))
|
||||
.then(() => log(new Date().getTime() - startTime));
|
||||
}
|
||||
|
||||
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
|
||||
|
||||
@@ -155,7 +155,7 @@ export interface IExtensionGalleryService {
|
||||
isEnabled(): boolean;
|
||||
query(token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<URI>;
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
|
||||
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
|
||||
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
|
||||
|
||||
104
src/vs/platform/extensionManagement/node/extensionDownloader.ts
Normal file
104
src/vs/platform/extensionManagement/node/extensionDownloader.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as semver from 'semver-umd';
|
||||
|
||||
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
|
||||
|
||||
export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
private readonly extensionsDownloadDir: URI = URI.file(tmpdir());
|
||||
private readonly cache: number = 0;
|
||||
private readonly cleanUpPromise: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
if (environmentService.extensionsDownloadPath) {
|
||||
this.extensionsDownloadDir = URI.file(environmentService.extensionsDownloadPath);
|
||||
this.cache = 20; // Cache 20 downloads
|
||||
this.cleanUpPromise = this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
await this.cleanUpPromise;
|
||||
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
|
||||
await this.download(extension, location, operation);
|
||||
return location;
|
||||
}
|
||||
|
||||
async delete(location: URI): Promise<void> {
|
||||
// Delete immediately if caching is disabled
|
||||
if (!this.cache) {
|
||||
await this.fileService.del(location);
|
||||
}
|
||||
}
|
||||
|
||||
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
if (!await this.fileService.exists(location)) {
|
||||
await this.extensionGalleryService.download(extension, location, operation);
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanUp(): Promise<void> {
|
||||
try {
|
||||
if (!(await this.fileService.exists(this.extensionsDownloadDir))) {
|
||||
this.logService.trace('Extension VSIX downlads cache dir does not exist');
|
||||
return;
|
||||
}
|
||||
const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true });
|
||||
if (folderStat.children) {
|
||||
const toDelete: URI[] = [];
|
||||
const all: [ExtensionIdentifierWithVersion, IFileStatWithMetadata][] = [];
|
||||
for (const stat of folderStat.children) {
|
||||
const extension = this.parse(stat.name);
|
||||
if (extension) {
|
||||
all.push([extension, stat]);
|
||||
}
|
||||
}
|
||||
const byExtension = groupByExtension(all, ([extension]) => extension.identifier);
|
||||
const distinct: IFileStatWithMetadata[] = [];
|
||||
for (const p of byExtension) {
|
||||
p.sort((a, b) => semver.rcompare(a[0].version, b[0].version));
|
||||
toDelete.push(...p.slice(1).map(e => e[1].resource)); // Delete outdated extensions
|
||||
distinct.push(p[0][1]);
|
||||
}
|
||||
distinct.sort((a, b) => a.mtime - b.mtime); // sort by modified time
|
||||
toDelete.push(...distinct.slice(0, Math.max(0, distinct.length - this.cache)).map(s => s.resource)); // Retain minimum cacheSize and delete the rest
|
||||
await Promise.all(toDelete.map(resource => {
|
||||
this.logService.trace('Deleting vsix from cache', resource.path);
|
||||
return this.fileService.del(resource);
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private getName(extension: IGalleryExtension): string {
|
||||
return this.cache ? new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key().toLowerCase() : generateUuid();
|
||||
}
|
||||
|
||||
private parse(name: string): ExtensionIdentifierWithVersion | null {
|
||||
const matches = ExtensionIdVersionRegex.exec(name);
|
||||
return matches && matches[1] && matches[2] ? new ExtensionIdentifierWithVersion({ id: matches[1] }, matches[2]) : null;
|
||||
}
|
||||
}
|
||||
@@ -40,12 +40,13 @@ import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'
|
||||
import { tmpdir } from 'os';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { optional, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
|
||||
|
||||
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
|
||||
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
|
||||
@@ -113,6 +114,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
private readonly installingExtensions: Map<string, CancelablePromise<ILocalExtension>> = new Map<string, CancelablePromise<ILocalExtension>>();
|
||||
private readonly uninstallingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
|
||||
private readonly manifestCache: ExtensionsManifestCache;
|
||||
private readonly extensionsDownloader: ExtensionsDownloader;
|
||||
private readonly extensionLifecycle: ExtensionsLifecycle;
|
||||
|
||||
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
|
||||
@@ -133,6 +135,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@optional(IDownloadService) private downloadService: IDownloadService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
|
||||
@@ -140,6 +143,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
|
||||
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
|
||||
this.extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService));
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
@@ -347,7 +351,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
this.downloadInstallableExtension(extension, operation)
|
||||
.then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken)
|
||||
.then(local => pfs.rimraf(installableExtension.zipPath).finally(() => { }).then(() => local)))
|
||||
.then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local)))
|
||||
.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
|
||||
.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
|
||||
.then(
|
||||
@@ -425,7 +429,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
};
|
||||
|
||||
this.logService.trace('Started downloading extension:', extension.identifier.id);
|
||||
return this.galleryService.download(extension, URI.file(tmpdir()), operation)
|
||||
return this.extensionsDownloader.downloadExtension(extension, operation)
|
||||
.then(
|
||||
zip => {
|
||||
const zipPath = zip.fsPath;
|
||||
@@ -1003,6 +1007,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
|
||||
this.telemetryService.publicLogError(eventName, assign(extensionData, { success: !error, duration, errorcode }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,8 +320,10 @@ export interface INotificationService {
|
||||
* Shows a prompt in the notification area with the provided choices. The prompt
|
||||
* is non-modal. If you want to show a modal dialog instead, use `IDialogService`.
|
||||
*
|
||||
* @param onCancel will be called if the user closed the notification without picking
|
||||
* any of the provided choices.
|
||||
* @param severity the severity of the notification. Either `Info`, `Warning` or `Error`.
|
||||
* @param message the message to show as status.
|
||||
* @param choices options to be choosen from.
|
||||
* @param options provides some optional configuration options.
|
||||
*
|
||||
* @returns a handle on the notification to e.g. hide it or update message, buttons, etc.
|
||||
*/
|
||||
|
||||
@@ -120,7 +120,7 @@ export default abstract class BaseErrorTelemetry {
|
||||
private _flushBuffer(): void {
|
||||
/*for (let error of this._buffer) { {{SQL CARBON EDIT}} don't log errors
|
||||
type UnhandledErrorClassification = {} & ErrorEventFragment;
|
||||
this._telemetryService.publicLog2<ErrorEvent, UnhandledErrorClassification>('UnhandledError', error, true);
|
||||
this._telemetryService.publicLogError2<ErrorEvent, UnhandledErrorClassification>('UnhandledError', error);
|
||||
}*/
|
||||
this._buffer.length = 0;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ export interface ITelemetryService {
|
||||
|
||||
publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void>;
|
||||
|
||||
publicLogError(errorEventName: string, data?: ITelemetryData): Promise<void>;
|
||||
|
||||
publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>): Promise<void>;
|
||||
|
||||
setEnabled(value: boolean): void;
|
||||
|
||||
getTelemetryInfo(): Promise<ITelemetryInfo>;
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla
|
||||
|
||||
export interface ITelemetryServiceConfig {
|
||||
appender: ITelemetryAppender;
|
||||
sendErrorTelemetry?: boolean;
|
||||
commonProperties?: Promise<{ [name: string]: any }>;
|
||||
piiPaths?: string[];
|
||||
trueMachineId?: string;
|
||||
@@ -34,6 +35,7 @@ export class TelemetryService implements ITelemetryService {
|
||||
private _piiPaths: string[];
|
||||
private _userOptIn: boolean;
|
||||
private _enabled: boolean;
|
||||
private _sendErrorTelemetry: boolean;
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private _cleanupPatterns: RegExp[] = [];
|
||||
@@ -47,6 +49,7 @@ export class TelemetryService implements ITelemetryService {
|
||||
this._piiPaths = config.piiPaths || [];
|
||||
this._userOptIn = true;
|
||||
this._enabled = true;
|
||||
this._sendErrorTelemetry = !!config.sendErrorTelemetry;
|
||||
|
||||
// static cleanup pattern for: `file:///DANGEROUS/PATH/resources/app/Useful/Information`
|
||||
this._cleanupPatterns = [/file:\/\/\/.*?\/resources\/app\//gi];
|
||||
@@ -144,6 +147,19 @@ export class TelemetryService implements ITelemetryService {
|
||||
return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths);
|
||||
}
|
||||
|
||||
publicLogError(errorEventName: string, data?: ITelemetryData): Promise<any> {
|
||||
if (!this._sendErrorTelemetry) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Send error event and anonymize paths
|
||||
return this.publicLog(errorEventName, data, true);
|
||||
}
|
||||
|
||||
publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>): Promise<any> {
|
||||
return this.publicLogError(eventName, data as ITelemetryData);
|
||||
}
|
||||
|
||||
private _cleanupInfo(stack: string, anonymizeFilePaths?: boolean): string {
|
||||
let updatedStack = stack;
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ export const NullTelemetryService = new class implements ITelemetryService {
|
||||
publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
return this.publicLog(eventName, data as ITelemetryData);
|
||||
}
|
||||
publicLogError(eventName: string, data?: ITelemetryData) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
return this.publicLogError(eventName, data as ITelemetryData);
|
||||
}
|
||||
|
||||
setEnabled() { }
|
||||
isOptedIn = true;
|
||||
getTelemetryInfo(): Promise<ITelemetryInfo> {
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
|
||||
import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import * as Errors from 'vs/base/common/errors';
|
||||
import * as sinon from 'sinon';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
class TestTelemetryAppender implements ITelemetryAppender {
|
||||
|
||||
@@ -208,6 +209,10 @@ suite('TelemetryService', () => {
|
||||
|
||||
private readonly promises: Promise<void>[] = [];
|
||||
|
||||
constructor(config: ITelemetryServiceConfig, configurationService: IConfigurationService) {
|
||||
super({ ...config, sendErrorTelemetry: true }, configurationService);
|
||||
}
|
||||
|
||||
join(): Promise<any> {
|
||||
return Promise.all(this.promises);
|
||||
}
|
||||
|
||||
@@ -343,6 +343,7 @@ export const diffInsertedOutline = registerColor('diffEditor.insertedTextBorder'
|
||||
export const diffRemovedOutline = registerColor('diffEditor.removedTextBorder', { dark: null, light: null, hc: '#FF008F' }, nls.localize('diffEditorRemovedOutline', 'Outline color for text that got removed.'));
|
||||
|
||||
export const diffBorder = registerColor('diffEditor.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('diffEditorBorder', 'Border color between the two text editors.'));
|
||||
export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', { dark: '#cccccc33', light: '#22222233', hc: null }, nls.localize('diffDiagonalFill', "Color of the diff editor's diagonal fill. The diagonal fill is used in side-by-side diff views."));
|
||||
|
||||
/**
|
||||
* List and tree colors
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as Codicons from 'vs/base/common/codicons';
|
||||
|
||||
// ------ API types
|
||||
|
||||
@@ -96,7 +97,7 @@ class IconRegistry implements IIconRegistry {
|
||||
|
||||
public registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon {
|
||||
if (!description) {
|
||||
description = localize('icon.defaultDescription', 'Icon with identifier {0}', id);
|
||||
description = localize('icon.defaultDescription', 'Icon with identifier \'{0}\'', id);
|
||||
}
|
||||
let iconContribution: IconContribution = { id, description, defaults, deprecationMessage };
|
||||
this.iconsById[id] = iconContribution;
|
||||
@@ -163,8 +164,13 @@ export function getIconRegistry(): IIconRegistry {
|
||||
return iconRegistry;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function initialize() {
|
||||
for (const icon of Codicons.iconRegistry.all) {
|
||||
registerIcon(icon.id, icon.definition);
|
||||
}
|
||||
Codicons.iconRegistry.onDidRegister(icon => registerIcon(icon.id, icon.definition));
|
||||
}
|
||||
initialize();
|
||||
|
||||
|
||||
export const iconsSchemaId = 'vscode://schemas/icons';
|
||||
|
||||
@@ -24,7 +24,7 @@ export const typeAndModifierIdPattern = `^${idPattern}$`;
|
||||
|
||||
export const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(\\${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`;
|
||||
|
||||
export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$';
|
||||
export const fontStylePattern = '^(\\s*(italic|bold|underline))*\\s*$';
|
||||
|
||||
export interface TokenSelector {
|
||||
match(type: string, modifiers: string[], language: string): number;
|
||||
@@ -90,30 +90,24 @@ export namespace TokenStyle {
|
||||
export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }): TokenStyle {
|
||||
return new TokenStyle(data.foreground, data.bold, data.underline, data.italic);
|
||||
}
|
||||
export function fromSettings(foreground: string | undefined, fontStyle: string | undefined): TokenStyle {
|
||||
export function fromSettings(foreground: string | undefined, fontStyle: string | undefined, bold?: boolean, underline?: boolean, italic?: boolean): TokenStyle {
|
||||
let foregroundColor = undefined;
|
||||
if (foreground !== undefined) {
|
||||
foregroundColor = Color.fromHex(foreground);
|
||||
}
|
||||
let bold, underline, italic;
|
||||
if (fontStyle !== undefined) {
|
||||
fontStyle = fontStyle.trim();
|
||||
if (fontStyle.length === 0) {
|
||||
bold = italic = underline = false;
|
||||
} else {
|
||||
const expression = /-?italic|-?bold|-?underline/g;
|
||||
let match;
|
||||
while ((match = expression.exec(fontStyle))) {
|
||||
switch (match[0]) {
|
||||
case 'bold': bold = true; break;
|
||||
case 'italic': italic = true; break;
|
||||
case 'underline': underline = true; break;
|
||||
}
|
||||
bold = italic = underline = false;
|
||||
const expression = /italic|bold|underline/g;
|
||||
let match;
|
||||
while ((match = expression.exec(fontStyle))) {
|
||||
switch (match[0]) {
|
||||
case 'bold': bold = true; break;
|
||||
case 'italic': italic = true; break;
|
||||
case 'underline': underline = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new TokenStyle(foregroundColor, bold, underline, italic);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,18 +124,18 @@ export interface TokenStyleDefaults {
|
||||
hc?: TokenStyleValue;
|
||||
}
|
||||
|
||||
export interface TokenStylingDefaultRule {
|
||||
export interface SemanticTokenDefaultRule {
|
||||
selector: TokenSelector;
|
||||
defaults: TokenStyleDefaults;
|
||||
}
|
||||
|
||||
export interface TokenStylingRule {
|
||||
export interface SemanticTokenRule {
|
||||
style: TokenStyle;
|
||||
selector: TokenSelector;
|
||||
}
|
||||
|
||||
export namespace TokenStylingRule {
|
||||
export function fromJSONObject(registry: ITokenClassificationRegistry, o: any): TokenStylingRule | undefined {
|
||||
export namespace SemanticTokenRule {
|
||||
export function fromJSONObject(registry: ITokenClassificationRegistry, o: any): SemanticTokenRule | undefined {
|
||||
if (o && typeof o._selector === 'string' && o._style) {
|
||||
const style = TokenStyle.fromJSONObject(o._style);
|
||||
if (style) {
|
||||
@@ -153,13 +147,13 @@ export namespace TokenStylingRule {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function toJSONObject(rule: TokenStylingRule): any {
|
||||
export function toJSONObject(rule: SemanticTokenRule): any {
|
||||
return {
|
||||
_selector: rule.selector.id,
|
||||
_style: TokenStyle.toJSONObject(rule.style)
|
||||
};
|
||||
}
|
||||
export function equals(r1: TokenStylingRule | undefined, r2: TokenStylingRule | undefined) {
|
||||
export function equals(r1: SemanticTokenRule | undefined, r2: SemanticTokenRule | undefined) {
|
||||
if (r1 === r2) {
|
||||
return true;
|
||||
}
|
||||
@@ -167,7 +161,7 @@ export namespace TokenStylingRule {
|
||||
&& r1.selector && r2.selector && r1.selector.id === r2.selector.id
|
||||
&& TokenStyle.equals(r1.style, r2.style);
|
||||
}
|
||||
export function is(r: any): r is TokenStylingRule {
|
||||
export function is(r: any): r is SemanticTokenRule {
|
||||
return r && r.selector && typeof r.selector.selectorString === 'string' && TokenStyle.is(r.style);
|
||||
}
|
||||
}
|
||||
@@ -245,7 +239,7 @@ export interface ITokenClassificationRegistry {
|
||||
/**
|
||||
* The styling rules to used when a schema does not define any styling rules.
|
||||
*/
|
||||
getTokenStylingDefaultRules(): TokenStylingDefaultRule[];
|
||||
getTokenStylingDefaultRules(): SemanticTokenDefaultRule[];
|
||||
|
||||
/**
|
||||
* JSON schema for an object to assign styling to token classifications
|
||||
@@ -264,14 +258,18 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution };
|
||||
private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution };
|
||||
|
||||
private tokenStylingDefaultRules: TokenStylingDefaultRule[] = [];
|
||||
private tokenStylingDefaultRules: SemanticTokenDefaultRule[] = [];
|
||||
|
||||
private typeHierarchy: { [id: string]: string[] };
|
||||
|
||||
private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
|
||||
private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap, patternProperties: IJSONSchemaMap } = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: getStylingSchemeEntry(),
|
||||
patternProperties: {
|
||||
[selectorPattern]: getStylingSchemeEntry()
|
||||
},
|
||||
//errorMessage: nls.localize('schema.token.errors', 'Valid token selectors have the form (*|tokenType)(.tokenModifier)*(:tokenLanguage)?.'),
|
||||
additionalProperties: false,
|
||||
definitions: {
|
||||
style: {
|
||||
type: 'object',
|
||||
@@ -289,11 +287,24 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
},
|
||||
fontStyle: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets inherited settings.'),
|
||||
description: nls.localize('schema.token.fontStyle', 'Sets the all font styles of the rule: \'italic\', \'bold\' or \'underline\' or a combination. All styles that are not listed are unset. The empty string unsets all styles.'),
|
||||
pattern: fontStylePattern,
|
||||
patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets all styles.'),
|
||||
defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }]
|
||||
},
|
||||
bold: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('schema.token.bold', 'Sets or unsets the font style to bold. Note, the presence of \'fontStyle\' overrides this setting.'),
|
||||
},
|
||||
italic: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('schema.token.italic', 'Sets or unsets the font style to italic. Note, the presence of \'fontStyle\' overrides this setting.'),
|
||||
},
|
||||
underline: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('schema.token.underline', 'Sets or unsets the font style to underline. Note, the presence of \'fontStyle\' overrides this setting.'),
|
||||
}
|
||||
|
||||
},
|
||||
defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }]
|
||||
}
|
||||
@@ -318,7 +329,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, superType, description, deprecationMessage };
|
||||
this.tokenTypeById[id] = tokenStyleContribution;
|
||||
|
||||
this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage);
|
||||
const stylingSchemeEntry = getStylingSchemeEntry(description, deprecationMessage);
|
||||
this.tokenStylingSchema.properties[id] = stylingSchemeEntry;
|
||||
this.typeHierarchy = {};
|
||||
}
|
||||
|
||||
@@ -406,7 +418,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
return this.tokenStylingSchema;
|
||||
}
|
||||
|
||||
public getTokenStylingDefaultRules(): TokenStylingDefaultRule[] {
|
||||
public getTokenStylingDefaultRules(): SemanticTokenDefaultRule[] {
|
||||
return this.tokenStylingDefaultRules;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,8 +98,13 @@ export function merge(originalLocalContent: string, originalRemoteContent: strin
|
||||
return { conflictsSettings: [], localContent: updateIgnoredSettings(originalRemoteContent, originalLocalContent, ignoredSettings, formattingOptions), remoteContent: null, hasConflicts: false };
|
||||
}
|
||||
|
||||
/* remote and local has changed */
|
||||
/* local is empty and not synced before */
|
||||
if (baseContent === null && isEmpty(originalLocalContent)) {
|
||||
const localContent = areSame(originalLocalContent, originalRemoteContent, ignoredSettings) ? null : updateIgnoredSettings(originalRemoteContent, originalLocalContent, ignoredSettings, formattingOptions);
|
||||
return { conflictsSettings: [], localContent, remoteContent: null, hasConflicts: false };
|
||||
}
|
||||
|
||||
/* remote and local has changed */
|
||||
let localContent = originalLocalContent;
|
||||
let remoteContent = originalRemoteContent;
|
||||
const local = parse(originalLocalContent);
|
||||
@@ -258,6 +263,11 @@ export function areSame(localContent: string, remoteContent: string, ignoredSett
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isEmpty(content: string): boolean {
|
||||
const nodes = parseSettings(content);
|
||||
return nodes.length === 0;
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any> | null, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = from ? Object.keys(from).filter(key => !ignored.has(key)) : [];
|
||||
const toKeys = Object.keys(to).filter(key => !ignored.has(key));
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, ISyncResourceHandle } 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';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createCancelablePromise } from 'vs/base/common/async';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -160,10 +158,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
||||
if (localFileContent) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const content = edit(localFileContent.value.toString(), [CONFIGURATION_SYNC_STORE_KEY], undefined, formatUtils);
|
||||
const settings = parse(content);
|
||||
if (!isEmptyObject(settings)) {
|
||||
return true;
|
||||
}
|
||||
return !isEmpty(content);
|
||||
}
|
||||
} catch (error) {
|
||||
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
|
||||
|
||||
@@ -417,7 +417,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
for (const entry of stat.children || []) {
|
||||
const resource = entry.resource;
|
||||
const extension = extname(resource);
|
||||
if (extension === '.json' || extension === '.code-snippet') {
|
||||
if (extension === '.json' || extension === '.code-snippets') {
|
||||
const key = relativePath(this.snippetsFolder, resource)!;
|
||||
const content = await this.fileService.readFile(resource);
|
||||
snippets[key] = content;
|
||||
|
||||
@@ -42,7 +42,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
}
|
||||
|
||||
private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise<void> {
|
||||
const enabled = await this.isAutoSyncEnabled();
|
||||
const { enabled, reason } = await this.isAutoSyncEnabled();
|
||||
if (this.enabled === enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
this.resetFailures();
|
||||
if (stopIfDisabled) {
|
||||
this.userDataSyncService.stop();
|
||||
this.logService.info('Auto Sync: stopped.');
|
||||
this.logService.info('Auto Sync: stopped because', reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +95,18 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
}
|
||||
}
|
||||
|
||||
private async isAutoSyncEnabled(): Promise<boolean> {
|
||||
return this.userDataSyncEnablementService.isEnabled()
|
||||
&& this.userDataSyncService.status !== SyncStatus.Uninitialized
|
||||
&& !!(await this.authTokenService.getToken());
|
||||
private async isAutoSyncEnabled(): Promise<{ enabled: boolean, reason?: string }> {
|
||||
if (!this.userDataSyncEnablementService.isEnabled()) {
|
||||
return { enabled: false, reason: 'sync is disabled' };
|
||||
}
|
||||
if (this.userDataSyncService.status === SyncStatus.Uninitialized) {
|
||||
return { enabled: false, reason: 'sync is not initialized' };
|
||||
}
|
||||
const token = await this.authTokenService.getToken();
|
||||
if (!token) {
|
||||
return { enabled: false, reason: 'token is not avaialable' };
|
||||
}
|
||||
return { enabled: true };
|
||||
}
|
||||
|
||||
private resetFailures(): void {
|
||||
|
||||
@@ -26,19 +26,6 @@ import { isArray, isString, isObject } from 'vs/base/common/types';
|
||||
|
||||
export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
|
||||
|
||||
export interface ISyncConfiguration {
|
||||
sync: {
|
||||
enable: boolean,
|
||||
enableSettings: boolean,
|
||||
enableKeybindings: boolean,
|
||||
enableUIState: boolean,
|
||||
enableExtensions: boolean,
|
||||
keybindingsPerPlatform: boolean,
|
||||
ignoredExtensions: string[],
|
||||
ignoredSettings: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export function getDisallowedIgnoredSettings(): string[] {
|
||||
const allSettings = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
|
||||
return Object.keys(allSettings).filter(setting => !!allSettings[setting].disallowSyncIgnore);
|
||||
|
||||
@@ -723,6 +723,23 @@ suite('SettingsMerge - Merge', () => {
|
||||
assert.deepEqual(actual.conflictsSettings, expectedConflicts);
|
||||
assert.ok(actual.hasConflicts);
|
||||
});
|
||||
|
||||
test('merge when remote has comments and local is empty', async () => {
|
||||
const localContent = `
|
||||
{
|
||||
|
||||
}`;
|
||||
const remoteContent = stringify`
|
||||
{
|
||||
// this is a comment
|
||||
"a": 1,
|
||||
}`;
|
||||
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
|
||||
assert.equal(actual.localContent, remoteContent);
|
||||
assert.equal(actual.remoteContent, null);
|
||||
assert.equal(actual.conflictsSettings.length, 0);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
});
|
||||
});
|
||||
|
||||
suite('SettingsMerge - Compute Remote Content', () => {
|
||||
|
||||
@@ -597,7 +597,7 @@ suite('SnippetsSync', () => {
|
||||
});
|
||||
|
||||
test('sync global and language snippet', async () => {
|
||||
await updateSnippet('global.code-snippet', globalSnippet, client2);
|
||||
await updateSnippet('global.code-snippets', globalSnippet, client2);
|
||||
await updateSnippet('html.json', htmlSnippet1, client2);
|
||||
await client2.sync();
|
||||
|
||||
@@ -607,17 +607,17 @@ suite('SnippetsSync', () => {
|
||||
|
||||
const actual1 = await readSnippet('html.json', testClient);
|
||||
assert.equal(actual1, htmlSnippet1);
|
||||
const actual2 = await readSnippet('global.code-snippet', testClient);
|
||||
const actual2 = await readSnippet('global.code-snippets', testClient);
|
||||
assert.equal(actual2, globalSnippet);
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseSnippets(content!);
|
||||
assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippet': globalSnippet });
|
||||
assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippets': globalSnippet });
|
||||
});
|
||||
|
||||
test('sync should ignore non snippets', async () => {
|
||||
await updateSnippet('global.code-snippet', globalSnippet, client2);
|
||||
await updateSnippet('global.code-snippets', globalSnippet, client2);
|
||||
await updateSnippet('html.html', htmlSnippet1, client2);
|
||||
await updateSnippet('typescript.json', tsSnippet1, client2);
|
||||
await client2.sync();
|
||||
@@ -628,7 +628,7 @@ suite('SnippetsSync', () => {
|
||||
|
||||
const actual1 = await readSnippet('typescript.json', testClient);
|
||||
assert.equal(actual1, tsSnippet1);
|
||||
const actual2 = await readSnippet('global.code-snippet', testClient);
|
||||
const actual2 = await readSnippet('global.code-snippets', testClient);
|
||||
assert.equal(actual2, globalSnippet);
|
||||
const actual3 = await readSnippet('html.html', testClient);
|
||||
assert.equal(actual3, null);
|
||||
@@ -636,7 +636,7 @@ suite('SnippetsSync', () => {
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseSnippets(content!);
|
||||
assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippet': globalSnippet });
|
||||
assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippets': globalSnippet });
|
||||
});
|
||||
|
||||
function parseSnippets(content: string): IStringDictionary<string> {
|
||||
|
||||
Reference in New Issue
Block a user