Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f

This commit is contained in:
ADS Merger
2020-07-22 03:06:57 +00:00
parent 53ec7585a9
commit 1b7b54ce14
229 changed files with 5099 additions and 3188 deletions

View File

@@ -5,20 +5,68 @@
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IActivityService, IActivity } from 'vs/workbench/services/activity/common/activity';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
import { GLOBAL_ACTIVITY_ID, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity';
import { Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class ViewContainerActivityByView extends Disposable {
private activity: IActivity | undefined = undefined;
private activityDisposable: IDisposable = Disposable.None;
constructor(
private readonly viewId: string,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IActivityService private readonly activityService: IActivityService,
) {
super();
this._register(Event.filter(this.viewDescriptorService.onDidChangeContainer, e => e.views.some(view => view.id === viewId))(() => this.update()));
this._register(Event.filter(this.viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => this.update()));
}
setActivity(activity: IActivity): void {
this.activity = activity;
this.update();
}
clearActivity(): void {
this.activity = undefined;
this.update();
}
private update(): void {
this.activityDisposable.dispose();
const container = this.viewDescriptorService.getViewContainerByViewId(this.viewId);
if (container && this.activity) {
this.activityDisposable = this.activityService.showViewContainerActivity(container.id, this.activity);
}
}
dispose() {
this.activityDisposable.dispose();
}
}
interface IViewActivity {
id: number;
readonly activity: ViewContainerActivityByView;
}
export class ActivityService implements IActivityService {
public _serviceBrand: undefined;
private viewActivities = new Map<string, IViewActivity>();
constructor(
@IPanelService private readonly panelService: IPanelService,
@IActivityBarService private readonly activityBarService: IActivityBarService,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) { }
showViewContainerActivity(viewContainerId: string, { badge, clazz, priority }: IActivity): IDisposable {
@@ -35,6 +83,32 @@ export class ActivityService implements IActivityService {
return Disposable.None;
}
showViewActivity(viewId: string, activity: IActivity): IDisposable {
let maybeItem = this.viewActivities.get(viewId);
if (maybeItem) {
maybeItem.id++;
} else {
maybeItem = {
id: 1,
activity: this.instantiationService.createInstance(ViewContainerActivityByView, viewId)
};
this.viewActivities.set(viewId, maybeItem);
}
const id = maybeItem.id;
maybeItem.activity.setActivity(activity);
const item = maybeItem;
return toDisposable(() => {
if (item.id === id) {
item.activity.dispose();
this.viewActivities.delete(viewId);
}
});
}
showAccountsActivity({ badge, clazz, priority }: IActivity): IDisposable {
return this.activityBarService.showActivity(ACCOUNTS_ACTIIVTY_ID, badge, clazz, priority);
}

View File

@@ -3,10 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { Event } from 'vs/base/common/event';
export interface IActivity {
readonly badge: IBadge;
@@ -25,6 +23,11 @@ export interface IActivityService {
*/
showViewContainerActivity(viewContainerId: string, badge: IActivity): IDisposable;
/**
* Show activity for the given view
*/
showViewActivity(viewId: string, badge: IActivity): IDisposable;
/**
* Show accounts activity
*/
@@ -36,39 +39,6 @@ export interface IActivityService {
showGlobalActivity(activity: IActivity): IDisposable;
}
export class ViewContaierActivityByView extends Disposable {
private activity: IActivity | undefined = undefined;
private activityDisposable: IDisposable = Disposable.None;
constructor(
private readonly viewId: string,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IActivityService private readonly activityService: IActivityService,
) {
super();
this._register(Event.filter(this.viewDescriptorService.onDidChangeContainer, e => e.views.some(view => view.id === viewId))(() => this.update()));
this._register(Event.filter(this.viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => this.update()));
}
setActivity(activity: IActivity): void {
this.activity = activity;
this.update();
}
private update(): void {
this.activityDisposable.dispose();
const container = this.viewDescriptorService.getViewContainerByViewId(this.viewId);
if (container && this.activity) {
this.activityDisposable = this.activityService.showViewContainerActivity(container.id, this.activity);
}
}
dispose() {
this.activityDisposable.dispose();
}
}
export interface IBadge {
getDescription(): string;
}

View File

@@ -94,7 +94,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
});
}));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas()));
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
if (environmentService.options?.configurationDefaults) {
configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]);
}
this._register(configurationRegistry.onDidSchemaChange(e => this.registerConfigurationSchemas()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this.workspaceEditingQueue = new Queue<void>();

View File

@@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { equals } from 'vs/base/common/objects';
import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration';
import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
export class WorkspaceConfigurationModelParser extends ConfigurationModelParser {

View File

@@ -25,7 +25,6 @@ import { coalesce } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ILabelService } from 'vs/platform/label/common/label';
import { isWindows } from 'vs/base/common/platform';
export abstract class AbstractFileDialogService implements IFileDialogService {
@@ -259,7 +258,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
// Build the file filter by using our known languages
const ext: string | undefined = defaultUri ? resources.extname(defaultUri) : undefined;
let matchingFilter: IFilter | undefined;
const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => {
const registeredLanguageFilters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => {
const extensions = this.modeService.getExtensions(languageName);
if (!extensions || !extensions.length) {
return null;
@@ -279,24 +278,20 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
// We have no matching filter, e.g. because the language
// is unknown. We still add the extension to the list of
// filters though so that it can be picked
// (https://github.com/microsoft/vscode/issues/96283) but
// only on Windows where this is an issue. Adding this to
// macOS would result in the following bugs:
// https://github.com/microsoft/vscode/issues/100614 and
// https://github.com/microsoft/vscode/issues/100241
if (isWindows && !matchingFilter && ext) {
// (https://github.com/microsoft/vscode/issues/96283)
if (!matchingFilter && ext) {
matchingFilter = { name: trim(ext, '.').toUpperCase(), extensions: [trim(ext, '.')] };
}
// Order of filters is
// - File Extension Match
// - All Files
// - All Files (we MUST do this to fix macOS issue https://github.com/microsoft/vscode/issues/102713)
// - File Extension Match (if any)
// - All Languages
// - No Extension
options.filters = coalesce([
matchingFilter,
{ name: nls.localize('allFiles', "All Files"), extensions: ['*'] },
...filters,
matchingFilter,
...registeredLanguageFilters,
{ name: nls.localize('noExt', "No Extension"), extensions: [''] }
]);

View File

@@ -874,16 +874,21 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Derive the label from the path if not provided explicitly
const label = resourceEditorInput.label || basename(resourceEditorInput.resource);
// We keep track of the preferred resource this input is to be created
// with but it may be different from the canonical resource (see below)
const preferredResource = resourceEditorInput.resource;
// From this moment on, only operate on the canonical resource
// to ensure we reduce the chance of opening the same resource
// with different resource forms (e.g. path casing on Windows)
const canonicalResource = this.asCanonicalEditorResource(resourceEditorInput.resource);
const canonicalResource = this.asCanonicalEditorResource(preferredResource);
return this.createOrGetCached(canonicalResource, () => {
// File
if (resourceEditorInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(canonicalResource)) {
return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, resourceEditorInput.resource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService);
if (resourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) {
return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService);
}
// Resource
@@ -897,7 +902,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Files
else if (!(cachedInput instanceof ResourceEditorInput)) {
cachedInput.setLabel(resourceEditorInput.resource);
cachedInput.setPreferredResource(preferredResource);
if (resourceEditorInput.encoding) {
cachedInput.setPreferredEncoding(resourceEditorInput.encoding);

View File

@@ -17,8 +17,8 @@ export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmen
readonly configuration: INativeEnvironmentConfiguration;
readonly disableCrashReporter: boolean;
readonly crashReporterDirectory?: string;
readonly crashReporterId?: string;
readonly cliPath: string;

View File

@@ -18,7 +18,9 @@ import { asText, isSuccess, IRequestService } from 'vs/platform/request/common/r
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { groupByExtension, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStaticExtension } from 'vs/workbench/workbench.web.api';
interface IUserExtension {
identifier: IExtensionIdentifier;
@@ -46,27 +48,60 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
declare readonly _serviceBrand: undefined;
private readonly systemExtensionsPromise: Promise<IScannedExtension[]>;
private readonly staticExtensions: IScannedExtension[];
private readonly extensionsResource: URI | undefined;
private readonly userExtensionsResourceLimiter: Queue<IUserExtension[]>;
private readonly systemExtensionsPromise: Promise<IScannedExtension[]> = Promise.resolve([]);
private readonly defaultExtensionsPromise: Promise<IScannedExtension[]> = Promise.resolve([]);
private readonly extensionsResource: URI | undefined = undefined;
private readonly userExtensionsResourceLimiter: Queue<IUserExtension[]> = new Queue<IUserExtension[]>();
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IBuiltinExtensionsScannerService private readonly builtinExtensionsScannerService: IBuiltinExtensionsScannerService,
@IFileService private readonly fileService: IFileService,
@IRequestService private readonly requestService: IRequestService,
@ILogService private readonly logService: ILogService,
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
this.extensionsResource = isWeb ? joinPath(environmentService.userRoamingDataHome, 'extensions.json') : undefined;
this.userExtensionsResourceLimiter = new Queue<IUserExtension[]>();
this.systemExtensionsPromise = isWeb ? this.builtinExtensionsScannerService.scanBuiltinExtensions() : Promise.resolve([]);
const staticExtensions = environmentService.options && Array.isArray(environmentService.options.staticExtensions) ? environmentService.options.staticExtensions : [];
this.staticExtensions = staticExtensions.map(data => <IScannedExtension>{
location: data.extensionLocation,
if (isWeb) {
this.extensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json');
this.systemExtensionsPromise = this.builtinExtensionsScannerService.scanBuiltinExtensions();
this.defaultExtensionsPromise = this.readDefaultExtensions();
}
}
private async readDefaultExtensions(): Promise<IScannedExtension[]> {
const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
const defaultUserWebExtensions = await this.readDefaultUserWebExtensions();
return [...staticExtensions, ...defaultUserWebExtensions].map<IScannedExtension>(e => ({
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
location: e.extensionLocation,
type: ExtensionType.User,
packageJSON: data.packageJSON,
});
packageJSON: e.packageJSON,
}));
}
private async readDefaultUserWebExtensions(): Promise<IStaticExtension[]> {
const result: IStaticExtension[] = [];
const defaultUserWebExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions') || [];
for (const webExtension of defaultUserWebExtensions) {
const extensionLocation = URI.parse(webExtension.location);
const manifestLocation = joinPath(extensionLocation, 'package.json');
const context = await this.requestService.request({ type: 'GET', url: manifestLocation.toString(true) }, CancellationToken.None);
if (!isSuccess(context)) {
this.logService.warn('Skipped default user web extension as there is an error while fetching manifest', manifestLocation);
continue;
}
const content = await asText(context);
if (!content) {
this.logService.warn('Skipped default user web extension as there is manifest is not found', manifestLocation);
continue;
}
const packageJSON = JSON.parse(content);
result.push({
packageJSON,
extensionLocation,
});
}
return result;
}
async scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]> {
@@ -76,7 +111,8 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
extensions.push(...systemExtensions);
}
if (type === undefined || type === ExtensionType.User) {
extensions.push(...this.staticExtensions);
const staticExtensions = await this.defaultExtensionsPromise;
extensions.push(...staticExtensions);
const userExtensions = await this.scanUserExtensions();
extensions.push(...userExtensions);
}

View File

@@ -587,8 +587,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!;
localProcessExtensionHost.start(localProcessExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
if (localProcessExtensionHost) {
localProcessExtensionHost.start(localProcessExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
}
const localWebWorkerExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalWebWorker);
if (localWebWorkerExtensionHost) {

View File

@@ -44,6 +44,7 @@ import { joinPath } from 'vs/base/common/resources';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { isUUID } from 'vs/base/common/uuid';
export interface ILocalProcessExtensionHostInitData {
readonly autoStart: boolean;
@@ -182,18 +183,23 @@ export class LocalProcessExtensionHost implements IExtensionHost {
opts.execArgv = ['--inspect-port=0'];
}
// Enable the crash reporter depending on environment for local reporting
const crashesDirectory = this._environmentService.crashReporterDirectory;
if (crashesDirectory) {
const crashReporterOptions: CrashReporterStartOptions = {
// On linux crash reporter needs to be started on child node processes explicitly
if (platform.isLinux) {
const crashReporterStartOptions: CrashReporterStartOptions = {
companyName: this._productService.crashReporter?.companyName || 'Microsoft',
productName: this._productService.crashReporter?.productName || this._productService.nameShort,
submitURL: '',
uploadToServer: false,
crashesDirectory
uploadToServer: false
};
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions);
const crashReporterId = this._environmentService.crashReporterId; // crashReporterId is set by the main process only when crash reporting is enabled by the user.
const appcenter = this._productService.appCenter;
const uploadCrashesToServer = !this._environmentService.crashReporterDirectory; // only upload unless --crash-reporter-directory is provided
if (uploadCrashesToServer && appcenter && crashReporterId && isUUID(crashReporterId)) {
const submitURL = appcenter[`linux-x64`];
crashReporterStartOptions.submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId);
crashReporterStartOptions.uploadToServer = true;
}
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
}
// Run Extension Host as fork of current process

View File

@@ -205,7 +205,7 @@ export class FileWalker {
.map(arg => arg.match(/^-/) ? arg : `'${arg}'`)
.join(' ');
let rgCmd = `rg ${escapedArgs}\n - cwd: ${ripgrep.cwd}`;
let rgCmd = `${ripgrep.rgDiskPath} ${escapedArgs}\n - cwd: ${ripgrep.cwd}`;
if (ripgrep.rgArgs.siblingClauses) {
rgCmd += `\n - Sibling clauses: ${JSON.stringify(ripgrep.rgArgs.siblingClauses)}`;
}

View File

@@ -23,6 +23,7 @@ export function spawnRipgrepCmd(config: IFileQuery, folderQuery: IFolderQuery, i
const cwd = folderQuery.folder.fsPath;
return {
cmd: cp.spawn(rgDiskPath, rgArgs.args, { cwd }),
rgDiskPath,
siblingClauses: rgArgs.siblingClauses,
rgArgs,
cwd

View File

@@ -44,7 +44,7 @@ export class RipgrepTextSearchEngine {
const escapedArgs = rgArgs
.map(arg => arg.match(/^-/) ? arg : `'${arg}'`)
.join(' ');
this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}`);
this.outputChannel.appendLine(`${rgDiskPath} ${escapedArgs}\n - cwd: ${cwd}`);
let rgProc: Maybe<cp.ChildProcess> = cp.spawn(rgDiskPath, rgArgs, { cwd });
rgProc.on('error', e => {
@@ -57,6 +57,7 @@ export class RipgrepTextSearchEngine {
const ripgrepParser = new RipgrepParser(options.maxResults, cwd, options.previewOptions);
ripgrepParser.on('result', (match: TextSearchResult) => {
gotResult = true;
dataWithoutResult = '';
progress.report(match);
});
@@ -79,8 +80,12 @@ export class RipgrepTextSearchEngine {
cancel();
});
let dataWithoutResult = '';
rgProc.stdout!.on('data', data => {
ripgrepParser.handleData(data);
if (!gotResult) {
dataWithoutResult += data;
}
});
let gotData = false;
@@ -96,7 +101,12 @@ export class RipgrepTextSearchEngine {
rgProc.on('close', () => {
this.outputChannel.appendLine(gotData ? 'Got data from stdout' : 'No data from stdout');
this.outputChannel.appendLine(gotResult ? 'Got result from parser' : 'No result from parser');
if (dataWithoutResult) {
this.outputChannel.appendLine(`Got data without result: ${dataWithoutResult}`);
}
this.outputChannel.appendLine('');
if (isDone) {
resolve({ limitHit });
} else {

View File

@@ -446,3 +446,244 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, aut
return { seemsBinary, encoding };
}
export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = {
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
},
utf8bom: {
labelLong: 'UTF-8 with BOM',
labelShort: 'UTF-8 with BOM',
encodeOnly: true,
order: 2,
alias: 'utf8'
},
utf16le: {
labelLong: 'UTF-16 LE',
labelShort: 'UTF-16 LE',
order: 3
},
utf16be: {
labelLong: 'UTF-16 BE',
labelShort: 'UTF-16 BE',
order: 4
},
windows1252: {
labelLong: 'Western (Windows 1252)',
labelShort: 'Windows 1252',
order: 5
},
iso88591: {
labelLong: 'Western (ISO 8859-1)',
labelShort: 'ISO 8859-1',
order: 6
},
iso88593: {
labelLong: 'Western (ISO 8859-3)',
labelShort: 'ISO 8859-3',
order: 7
},
iso885915: {
labelLong: 'Western (ISO 8859-15)',
labelShort: 'ISO 8859-15',
order: 8
},
macroman: {
labelLong: 'Western (Mac Roman)',
labelShort: 'Mac Roman',
order: 9
},
cp437: {
labelLong: 'DOS (CP 437)',
labelShort: 'CP437',
order: 10
},
windows1256: {
labelLong: 'Arabic (Windows 1256)',
labelShort: 'Windows 1256',
order: 11
},
iso88596: {
labelLong: 'Arabic (ISO 8859-6)',
labelShort: 'ISO 8859-6',
order: 12
},
windows1257: {
labelLong: 'Baltic (Windows 1257)',
labelShort: 'Windows 1257',
order: 13
},
iso88594: {
labelLong: 'Baltic (ISO 8859-4)',
labelShort: 'ISO 8859-4',
order: 14
},
iso885914: {
labelLong: 'Celtic (ISO 8859-14)',
labelShort: 'ISO 8859-14',
order: 15
},
windows1250: {
labelLong: 'Central European (Windows 1250)',
labelShort: 'Windows 1250',
order: 16
},
iso88592: {
labelLong: 'Central European (ISO 8859-2)',
labelShort: 'ISO 8859-2',
order: 17
},
cp852: {
labelLong: 'Central European (CP 852)',
labelShort: 'CP 852',
order: 18
},
windows1251: {
labelLong: 'Cyrillic (Windows 1251)',
labelShort: 'Windows 1251',
order: 19
},
cp866: {
labelLong: 'Cyrillic (CP 866)',
labelShort: 'CP 866',
order: 20
},
iso88595: {
labelLong: 'Cyrillic (ISO 8859-5)',
labelShort: 'ISO 8859-5',
order: 21
},
koi8r: {
labelLong: 'Cyrillic (KOI8-R)',
labelShort: 'KOI8-R',
order: 22
},
koi8u: {
labelLong: 'Cyrillic (KOI8-U)',
labelShort: 'KOI8-U',
order: 23
},
iso885913: {
labelLong: 'Estonian (ISO 8859-13)',
labelShort: 'ISO 8859-13',
order: 24
},
windows1253: {
labelLong: 'Greek (Windows 1253)',
labelShort: 'Windows 1253',
order: 25
},
iso88597: {
labelLong: 'Greek (ISO 8859-7)',
labelShort: 'ISO 8859-7',
order: 26
},
windows1255: {
labelLong: 'Hebrew (Windows 1255)',
labelShort: 'Windows 1255',
order: 27
},
iso88598: {
labelLong: 'Hebrew (ISO 8859-8)',
labelShort: 'ISO 8859-8',
order: 28
},
iso885910: {
labelLong: 'Nordic (ISO 8859-10)',
labelShort: 'ISO 8859-10',
order: 29
},
iso885916: {
labelLong: 'Romanian (ISO 8859-16)',
labelShort: 'ISO 8859-16',
order: 30
},
windows1254: {
labelLong: 'Turkish (Windows 1254)',
labelShort: 'Windows 1254',
order: 31
},
iso88599: {
labelLong: 'Turkish (ISO 8859-9)',
labelShort: 'ISO 8859-9',
order: 32
},
windows1258: {
labelLong: 'Vietnamese (Windows 1258)',
labelShort: 'Windows 1258',
order: 33
},
gbk: {
labelLong: 'Simplified Chinese (GBK)',
labelShort: 'GBK',
order: 34
},
gb18030: {
labelLong: 'Simplified Chinese (GB18030)',
labelShort: 'GB18030',
order: 35
},
cp950: {
labelLong: 'Traditional Chinese (Big5)',
labelShort: 'Big5',
order: 36
},
big5hkscs: {
labelLong: 'Traditional Chinese (Big5-HKSCS)',
labelShort: 'Big5-HKSCS',
order: 37
},
shiftjis: {
labelLong: 'Japanese (Shift JIS)',
labelShort: 'Shift JIS',
order: 38
},
eucjp: {
labelLong: 'Japanese (EUC-JP)',
labelShort: 'EUC-JP',
order: 39
},
euckr: {
labelLong: 'Korean (EUC-KR)',
labelShort: 'EUC-KR',
order: 40
},
windows874: {
labelLong: 'Thai (Windows 874)',
labelShort: 'Windows 874',
order: 41
},
iso885911: {
labelLong: 'Latin/Thai (ISO 8859-11)',
labelShort: 'ISO 8859-11',
order: 42
},
koi8ru: {
labelLong: 'Cyrillic (KOI8-RU)',
labelShort: 'KOI8-RU',
order: 43
},
koi8t: {
labelLong: 'Tajik (KOI8-T)',
labelShort: 'KOI8-T',
order: 44
},
gb2312: {
labelLong: 'Simplified Chinese (GB 2312)',
labelShort: 'GB 2312',
order: 45
},
cp865: {
labelLong: 'Nordic DOS (CP 865)',
labelShort: 'CP 865',
order: 46
},
cp850: {
labelLong: 'Western European DOS (CP 850)',
labelShort: 'CP 850',
order: 47
}
};

View File

@@ -75,6 +75,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
private bufferSavedVersionId: number | undefined;
private ignoreDirtyOnModelContentChange = false;
private static readonly UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD = 500;
private lastModelContentChangeFromUndoRedo: number | undefined = undefined;
private lastResolvedFileStat: IFileStatWithMetadata | undefined;
private readonly saveSequentializer = new TaskSequentializer();
@@ -450,16 +453,23 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// where `value` was captured in the content change listener closure scope.
// Content Change
this._register(model.onDidChangeContent(() => this.onModelContentChanged(model)));
this._register(model.onDidChangeContent(e => this.onModelContentChanged(model, e.isUndoing || e.isRedoing)));
}
private onModelContentChanged(model: ITextModel): void {
private onModelContentChanged(model: ITextModel, isUndoingOrRedoing: boolean): void {
this.logService.trace(`[text file model] onModelContentChanged() - enter`, this.resource.toString(true));
// In any case increment the version id because it tracks the textual content state of the model at all times
this.versionId++;
this.logService.trace(`[text file model] onModelContentChanged() - new versionId ${this.versionId}`, this.resource.toString(true));
// Remember when the user changed the model through a undo/redo operation.
// We need this information to throttle save participants to fix
// https://github.com/microsoft/vscode/issues/102542
if (isUndoingOrRedoing) {
this.lastModelContentChangeFromUndoRedo = Date.now();
}
// We mark check for a dirty-state change upon model content change, unless:
// - explicitly instructed to ignore it (e.g. from model.load())
// - the model is readonly (in that case we never assume the change was done by the user)
@@ -639,7 +649,31 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Save participants can also be skipped through API.
if (this.isResolved() && !options.skipSaveParticipants) {
try {
await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token);
// Measure the time it took from the last undo/redo operation to this save. If this
// time is below `UNDO_REDO_SAVE_PARTICIPANTS_THROTTLE_THRESHOLD`, we make sure to
// delay the save participant for the remaining time if the reason is auto save.
//
// This fixes the following issue:
// - the user has configured auto save with delay of 100ms or shorter
// - the user has a save participant enabled that modifies the file on each save
// - the user types into the file and the file gets saved
// - the user triggers undo operation
// - this will undo the save participant change but trigger the save participant right after
// - the user has no chance to undo over the save participant
//
// Reported as: https://github.com/microsoft/vscode/issues/102542
if (options.reason === SaveReason.AUTO && typeof this.lastModelContentChangeFromUndoRedo === 'number') {
const timeFromUndoRedoToSave = Date.now() - this.lastModelContentChangeFromUndoRedo;
if (timeFromUndoRedoToSave < TextFileEditorModel.UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD) {
await timeout(TextFileEditorModel.UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD - timeFromUndoRedoToSave);
}
}
// Run save participants unless save was cancelled meanwhile
if (!saveCancellation.token.isCancellationRequested) {
await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token);
}
} catch (error) {
this.logService.error(`[text file model] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(true));
}

View File

@@ -506,244 +506,3 @@ export function toBufferOrReadable(value: string | ITextSnapshot | undefined): V
return new TextSnapshotReadable(value);
}
export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = {
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
},
utf8bom: {
labelLong: 'UTF-8 with BOM',
labelShort: 'UTF-8 with BOM',
encodeOnly: true,
order: 2,
alias: 'utf8'
},
utf16le: {
labelLong: 'UTF-16 LE',
labelShort: 'UTF-16 LE',
order: 3
},
utf16be: {
labelLong: 'UTF-16 BE',
labelShort: 'UTF-16 BE',
order: 4
},
windows1252: {
labelLong: 'Western (Windows 1252)',
labelShort: 'Windows 1252',
order: 5
},
iso88591: {
labelLong: 'Western (ISO 8859-1)',
labelShort: 'ISO 8859-1',
order: 6
},
iso88593: {
labelLong: 'Western (ISO 8859-3)',
labelShort: 'ISO 8859-3',
order: 7
},
iso885915: {
labelLong: 'Western (ISO 8859-15)',
labelShort: 'ISO 8859-15',
order: 8
},
macroman: {
labelLong: 'Western (Mac Roman)',
labelShort: 'Mac Roman',
order: 9
},
cp437: {
labelLong: 'DOS (CP 437)',
labelShort: 'CP437',
order: 10
},
windows1256: {
labelLong: 'Arabic (Windows 1256)',
labelShort: 'Windows 1256',
order: 11
},
iso88596: {
labelLong: 'Arabic (ISO 8859-6)',
labelShort: 'ISO 8859-6',
order: 12
},
windows1257: {
labelLong: 'Baltic (Windows 1257)',
labelShort: 'Windows 1257',
order: 13
},
iso88594: {
labelLong: 'Baltic (ISO 8859-4)',
labelShort: 'ISO 8859-4',
order: 14
},
iso885914: {
labelLong: 'Celtic (ISO 8859-14)',
labelShort: 'ISO 8859-14',
order: 15
},
windows1250: {
labelLong: 'Central European (Windows 1250)',
labelShort: 'Windows 1250',
order: 16
},
iso88592: {
labelLong: 'Central European (ISO 8859-2)',
labelShort: 'ISO 8859-2',
order: 17
},
cp852: {
labelLong: 'Central European (CP 852)',
labelShort: 'CP 852',
order: 18
},
windows1251: {
labelLong: 'Cyrillic (Windows 1251)',
labelShort: 'Windows 1251',
order: 19
},
cp866: {
labelLong: 'Cyrillic (CP 866)',
labelShort: 'CP 866',
order: 20
},
iso88595: {
labelLong: 'Cyrillic (ISO 8859-5)',
labelShort: 'ISO 8859-5',
order: 21
},
koi8r: {
labelLong: 'Cyrillic (KOI8-R)',
labelShort: 'KOI8-R',
order: 22
},
koi8u: {
labelLong: 'Cyrillic (KOI8-U)',
labelShort: 'KOI8-U',
order: 23
},
iso885913: {
labelLong: 'Estonian (ISO 8859-13)',
labelShort: 'ISO 8859-13',
order: 24
},
windows1253: {
labelLong: 'Greek (Windows 1253)',
labelShort: 'Windows 1253',
order: 25
},
iso88597: {
labelLong: 'Greek (ISO 8859-7)',
labelShort: 'ISO 8859-7',
order: 26
},
windows1255: {
labelLong: 'Hebrew (Windows 1255)',
labelShort: 'Windows 1255',
order: 27
},
iso88598: {
labelLong: 'Hebrew (ISO 8859-8)',
labelShort: 'ISO 8859-8',
order: 28
},
iso885910: {
labelLong: 'Nordic (ISO 8859-10)',
labelShort: 'ISO 8859-10',
order: 29
},
iso885916: {
labelLong: 'Romanian (ISO 8859-16)',
labelShort: 'ISO 8859-16',
order: 30
},
windows1254: {
labelLong: 'Turkish (Windows 1254)',
labelShort: 'Windows 1254',
order: 31
},
iso88599: {
labelLong: 'Turkish (ISO 8859-9)',
labelShort: 'ISO 8859-9',
order: 32
},
windows1258: {
labelLong: 'Vietnamese (Windows 1258)',
labelShort: 'Windows 1258',
order: 33
},
gbk: {
labelLong: 'Simplified Chinese (GBK)',
labelShort: 'GBK',
order: 34
},
gb18030: {
labelLong: 'Simplified Chinese (GB18030)',
labelShort: 'GB18030',
order: 35
},
cp950: {
labelLong: 'Traditional Chinese (Big5)',
labelShort: 'Big5',
order: 36
},
big5hkscs: {
labelLong: 'Traditional Chinese (Big5-HKSCS)',
labelShort: 'Big5-HKSCS',
order: 37
},
shiftjis: {
labelLong: 'Japanese (Shift JIS)',
labelShort: 'Shift JIS',
order: 38
},
eucjp: {
labelLong: 'Japanese (EUC-JP)',
labelShort: 'EUC-JP',
order: 39
},
euckr: {
labelLong: 'Korean (EUC-KR)',
labelShort: 'EUC-KR',
order: 40
},
windows874: {
labelLong: 'Thai (Windows 874)',
labelShort: 'Windows 874',
order: 41
},
iso885911: {
labelLong: 'Latin/Thai (ISO 8859-11)',
labelShort: 'ISO 8859-11',
order: 42
},
koi8ru: {
labelLong: 'Cyrillic (KOI8-RU)',
labelShort: 'KOI8-RU',
order: 43
},
koi8t: {
labelLong: 'Tajik (KOI8-T)',
labelShort: 'KOI8-T',
order: 44
},
gb2312: {
labelLong: 'Simplified Chinese (GB 2312)',
labelShort: 'GB 2312',
order: 45
},
cp865: {
labelLong: 'Nordic DOS (CP 865)',
labelShort: 'CP 865',
order: 46
},
cp850: {
labelLong: 'Western European DOS (CP 850)',
labelShort: 'CP 850',
order: 47
}
};

View File

@@ -11,7 +11,6 @@ import * as streams from 'vs/base/common/stream';
import * as iconv from 'iconv-lite-umd';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
import { isWindows } from 'vs/base/common/platform';
export async function detectEncodingByBOM(file: string): Promise<typeof encoding.UTF16be | typeof encoding.UTF16le | typeof encoding.UTF8_with_bom | null> {
@@ -427,7 +426,7 @@ suite('Encoding', () => {
});
test('encodingExists', async function () {
for (const enc in SUPPORTED_ENCODINGS) {
for (const enc in encoding.SUPPORTED_ENCODINGS) {
if (enc === encoding.UTF8_with_bom) {
continue; // skip over encodings from us
}

View File

@@ -6,7 +6,7 @@
import { IUserDataSyncService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResourceGroup, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
@@ -270,7 +270,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined);
try {
switch (action) {
case 'merge': return await manualSyncTask.merge();
case 'merge': return await manualSyncTask.apply();
case 'pull': return await manualSyncTask.pull();
case 'push': return await manualSyncTask.push();
case 'manual': return;
@@ -302,28 +302,28 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
const result = await this.dialogService.show(
Severity.Info,
localize('Replace or Merge', "Replace or Merge"),
localize('preferences sync', "Preferences Sync"),
[
localize('sync manually', "Sync Manually"),
localize('merge', "Merge"),
localize('replace local', "Replace Local"),
localize('sync manually', "Sync Manually..."),
localize('cancel', "Cancel"),
],
{
cancelId: 3,
detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to replace or merge with the synced data?"),
detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to replace or merge with your data in the cloud or sync manually?"),
}
);
switch (result.choice) {
case 0:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'manual' });
return 'manual';
case 1:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' });
return 'merge';
case 2:
case 1:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'pull' });
return 'pull';
case 2:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'manual' });
return 'manual';
}
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
throw canceled();
@@ -337,14 +337,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
await this.waitForActiveSyncViews();
await this.viewsService.openView(MANUAL_SYNC_VIEW_ID);
await Event.toPromise(Event.filter(this.userDataSyncPreview.onDidChangeChanges, e => e.length === 0));
if (this.userDataSyncPreview.conflicts.length) {
await Event.toPromise(Event.filter(this.userDataSyncPreview.onDidChangeConflicts, e => e.length === 0));
}
/* Merge to sync globalState changes */
await task.merge();
const error = await Event.toPromise(this.userDataSyncPreview.onDidCompleteManualSync);
this.userDataSyncPreview.unsetManualSyncPreview();
this.manualSyncViewEnablementContext.set(false);
@@ -354,12 +347,16 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(MANUAL_SYNC_VIEW_ID);
this.viewsService.closeViewContainer(viewContainer!.id);
}
if (error) {
throw error;
}
}
async resetSyncedData(): Promise<void> {
const result = await this.dialogService.confirm({
message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."),
title: localize('reset title', "Reset Synced Data"),
message: localize('reset', "This will clear your data in the cloud and stop sync on all your devices."),
title: localize('reset title', "Clear"),
type: 'info',
primaryButton: localize('reset button', "Reset"),
});
@@ -560,16 +557,18 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
private _changes: ReadonlyArray<IUserDataSyncResourceGroup> = [];
get changes() { return Object.freeze(this._changes); }
private _onDidChangeChanges = this._register(new Emitter<ReadonlyArray<IUserDataSyncResourceGroup>>());
readonly onDidChangeChanges = this._onDidChangeChanges.event;
private _resources: ReadonlyArray<IUserDataSyncResource> = [];
get resources() { return Object.freeze(this._resources); }
private _onDidChangeResources = this._register(new Emitter<ReadonlyArray<IUserDataSyncResource>>());
readonly onDidChangeResources = this._onDidChangeResources.event;
private _conflicts: ReadonlyArray<IUserDataSyncResourceGroup> = [];
private _conflicts: ReadonlyArray<IUserDataSyncResource> = [];
get conflicts() { return Object.freeze(this._conflicts); }
private _onDidChangeConflicts = this._register(new Emitter<ReadonlyArray<IUserDataSyncResourceGroup>>());
private _onDidChangeConflicts = this._register(new Emitter<ReadonlyArray<IUserDataSyncResource>>());
readonly onDidChangeConflicts = this._onDidChangeConflicts.event;
private _onDidCompleteManualSync = this._register(new Emitter<Error | undefined>());
readonly onDidCompleteManualSync = this._onDidCompleteManualSync.event;
private manualSync: { preview: [SyncResource, ISyncResourcePreview][], task: IManualSyncTask, disposables: DisposableStore } | undefined;
constructor(
@@ -583,7 +582,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
setManualSyncPreview(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): void {
const disposables = new DisposableStore();
this.manualSync = { task, preview, disposables };
this.updateChanges();
this.updateResources();
}
unsetManualSyncPreview(): void {
@@ -591,19 +590,19 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
this.manualSync.disposables.dispose();
this.manualSync = undefined;
}
this.updateChanges();
this.updateResources();
}
async accept(syncResource: SyncResource, resource: URI, content: string): Promise<void> {
async accept(syncResource: SyncResource, resource: URI, content: string | null): Promise<void> {
if (this.manualSync) {
const syncPreview = await this.manualSync.task.accept(resource, content);
this.updatePreview(syncPreview);
} else {
await this.userDataSyncService.acceptPreviewContent(syncResource, resource, content);
await this.userDataSyncService.accept(syncResource, resource, content, false);
}
}
async merge(resource?: URI): Promise<void> {
async merge(resource: URI): Promise<void> {
if (!this.manualSync) {
throw new Error('Can merge only while syncing manually');
}
@@ -611,6 +610,41 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
this.updatePreview(syncPreview);
}
async discard(resource: URI): Promise<void> {
if (!this.manualSync) {
throw new Error('Can discard only while syncing manually');
}
const syncPreview = await this.manualSync.task.discard(resource);
this.updatePreview(syncPreview);
}
async apply(): Promise<void> {
if (!this.manualSync) {
throw new Error('Can apply only while syncing manually');
}
try {
const syncPreview = await this.manualSync.task.apply();
this.updatePreview(syncPreview);
if (!this._resources.length) {
this._onDidCompleteManualSync.fire(undefined);
}
} catch (error) {
await this.manualSync.task.stop();
this.updatePreview([]);
this._onDidCompleteManualSync.fire(error);
}
}
async cancel(): Promise<void> {
if (!this.manualSync) {
throw new Error('Can cancel only while syncing manually');
}
await this.manualSync.task.stop();
this.updatePreview([]);
this._onDidCompleteManualSync.fire(canceled());
}
async pull(): Promise<void> {
if (!this.manualSync) {
throw new Error('Can pull only while syncing manually');
@@ -630,7 +664,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
private updatePreview(preview: [SyncResource, ISyncResourcePreview][]) {
if (this.manualSync) {
this.manualSync.preview = preview;
this.updateChanges();
this.updateResources();
}
}
@@ -640,34 +674,28 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
this._conflicts = newConflicts;
this._onDidChangeConflicts.fire(this.conflicts);
}
this.updateChanges();
}
private updateChanges(): void {
const newChanges = this.toUserDataSyncResourceGroups(
private updateResources(): void {
const newResources = this.toUserDataSyncResourceGroups(
(this.manualSync?.preview || [])
.filter(([syncResource]) => syncResource !== SyncResource.GlobalState) /* Filter Global State Changes */
.map(([syncResource, syncResourcePreview]) =>
([
syncResource,
/* remove merged previews and conflicts and with no changes and conflicts */
syncResourcePreview.resourcePreviews.filter(r =>
!r.merged
&& (r.localChange !== Change.None || r.remoteChange !== Change.None)
&& !this._conflicts.some(c => c.syncResource === syncResource && isEqual(c.local, r.localResource)))
syncResourcePreview.resourcePreviews
]))
);
if (!equals(newChanges, this._changes, (a, b) => isEqual(a.local, b.local))) {
this._changes = newChanges;
this._onDidChangeChanges.fire(this.changes);
if (!equals(newResources, this._resources, (a, b) => isEqual(a.local, b.local) && a.mergeState === b.mergeState)) {
this._resources = newResources;
this._onDidChangeResources.fire(this.resources);
}
}
private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResourceGroup[] {
private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResource[] {
return flatten(
syncResourcePreviews.map(([syncResource, resourcePreviews]) =>
resourcePreviews.map<IUserDataSyncResourceGroup>(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
({ syncResource, local: localResource, remote: remoteResource, preview: previewResource, localChange, remoteChange })))
resourcePreviews.map<IUserDataSyncResource>(({ localResource, remoteResource, previewResource, acceptedResource, localChange, remoteChange, mergeState }) =>
({ syncResource, local: localResource, remote: remoteResource, merged: previewResource, accepted: acceptedResource, localChange, remoteChange, mergeState })))
);
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IAuthenticationProvider, SyncStatus, SyncResource, Change } from 'vs/platform/userDataSync/common/userDataSync';
import { IAuthenticationProvider, SyncStatus, SyncResource, Change, MergeState } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
@@ -17,25 +17,27 @@ export interface IUserDataSyncAccount {
}
export interface IUserDataSyncPreview {
readonly onDidChangeChanges: Event<ReadonlyArray<IUserDataSyncResourceGroup>>;
readonly changes: ReadonlyArray<IUserDataSyncResourceGroup>;
readonly onDidChangeResources: Event<ReadonlyArray<IUserDataSyncResource>>;
readonly resources: ReadonlyArray<IUserDataSyncResource>;
onDidChangeConflicts: Event<ReadonlyArray<IUserDataSyncResourceGroup>>;
readonly conflicts: ReadonlyArray<IUserDataSyncResourceGroup>;
accept(syncResource: SyncResource, resource: URI, content: string): Promise<void>;
accept(syncResource: SyncResource, resource: URI, content: string | null): Promise<void>;
merge(resource?: URI): Promise<void>;
discard(resource?: URI): Promise<void>;
pull(): Promise<void>;
push(): Promise<void>;
apply(): Promise<void>;
cancel(): Promise<void>;
}
export interface IUserDataSyncResourceGroup {
export interface IUserDataSyncResource {
readonly syncResource: SyncResource;
readonly local: URI;
readonly remote: URI;
readonly preview: URI;
readonly merged: URI;
readonly accepted: URI;
readonly localChange: Change;
readonly remoteChange: Change;
readonly mergeState: MergeState;
}
export const IUserDataSyncWorkbenchService = createDecorator<IUserDataSyncWorkbenchService>('IUserDataSyncWorkbenchService');

View File

@@ -102,8 +102,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('hasLocalData');
}
acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string): Promise<void> {
return this.channel.call('acceptPreviewContent', [syncResource, resource, content]);
accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise<void> {
return this.channel.call('accept', [syncResource, resource, content, apply]);
}
resolveContent(resource: URI): Promise<string | null> {
@@ -186,16 +186,26 @@ class ManualSyncTask implements IManualSyncTask {
return this.deserializePreviews(previews);
}
async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> {
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('accept', [resource, content]);
return this.deserializePreviews(previews);
}
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('merge', [resource]);
return this.deserializePreviews(previews);
}
async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('discard', [resource]);
return this.deserializePreviews(previews);
}
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('apply');
return this.deserializePreviews(previews);
}
pull(): Promise<void> {
return this.channel.call('pull');
}
@@ -223,6 +233,7 @@ class ManualSyncTask implements IManualSyncTask {
localResource: URI.revive(r.localResource),
remoteResource: URI.revive(r.remoteResource),
previewResource: URI.revive(r.previewResource),
acceptedResource: URI.revive(r.acceptedResource),
}))
}
]));