mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-25 15:35:43 +01:00
movw keybindings merge to workbench as service
This commit is contained in:
@@ -50,7 +50,7 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
@@ -63,6 +63,7 @@ import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenSer
|
||||
import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc';
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
|
||||
import { KeybindingsMergeChannelClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -186,6 +187,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
|
||||
const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter);
|
||||
services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel));
|
||||
const keybindingsMergeChannel = server.getChannel('keybindingsMerge', activeWindowRouter);
|
||||
services.set(IKeybindingsMergeService, new KeybindingsMergeChannelClient(keybindingsMergeChannel));
|
||||
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
registerConfiguration();
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json';
|
||||
import { values, keys } from 'vs/base/common/map';
|
||||
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import * as contentUtil from 'vs/platform/userDataSync/common/content';
|
||||
|
||||
export function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } {
|
||||
const local = <IUserFriendlyKeybinding[]>parse(localContent);
|
||||
const remote = <IUserFriendlyKeybinding[]>parse(remoteContent);
|
||||
const base = baseContent ? <IUserFriendlyKeybinding[]>parse(baseContent) : null;
|
||||
|
||||
const byCommand = (keybindings: IUserFriendlyKeybinding[]) => {
|
||||
const map: Map<string, IUserFriendlyKeybinding[]> = new Map<string, IUserFriendlyKeybinding[]>();
|
||||
for (const keybinding of keybindings) {
|
||||
const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command;
|
||||
let value = map.get(command);
|
||||
if (!value) {
|
||||
value = [];
|
||||
map.set(command, value);
|
||||
}
|
||||
value.push(keybinding);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
const localByCommand = byCommand(local);
|
||||
const remoteByCommand = byCommand(remote);
|
||||
const baseByCommand = base ? byCommand(base) : null;
|
||||
|
||||
const localToRemote = compare(localByCommand, remoteByCommand);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { mergeContent: localContent, hasChanges: false, hasConflicts: false };
|
||||
}
|
||||
|
||||
const conflictCommands: Set<string> = new Set<string>();
|
||||
const baseToLocal = baseByCommand ? compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = baseByCommand ? compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const eol = contentUtil.getEol(localContent);
|
||||
let mergeContent = localContent;
|
||||
|
||||
// Removed commands in Local
|
||||
for (const command of values(baseToLocal.removed)) {
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed commands in Remote
|
||||
for (const command of values(baseToRemote.removed)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
} else {
|
||||
// remove the command
|
||||
mergeContent = removeKeybindings(mergeContent, eol, command);
|
||||
}
|
||||
}
|
||||
|
||||
// Added commands in Local
|
||||
for (const command of values(baseToLocal.added)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in remote
|
||||
if (baseToRemote.added.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Added commands in remote
|
||||
for (const command of values(baseToRemote.added)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
} else {
|
||||
mergeContent = addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!);
|
||||
}
|
||||
}
|
||||
|
||||
// Updated commands in Local
|
||||
for (const command of values(baseToLocal.updated)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated commands in Remote
|
||||
for (const command of values(baseToRemote.updated)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
} else {
|
||||
// update the command
|
||||
mergeContent = updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!);
|
||||
}
|
||||
}
|
||||
|
||||
const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = [];
|
||||
|
||||
for (const command of values(conflictCommands)) {
|
||||
const local = localByCommand.get(command);
|
||||
const remote = remoteByCommand.get(command);
|
||||
mergeContent = updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]);
|
||||
conflicts.push({ command, local, remote, firstIndex: -1 });
|
||||
}
|
||||
|
||||
const allKeybindings = <IUserFriendlyKeybinding[]>parse(mergeContent);
|
||||
for (const conflict of conflicts) {
|
||||
conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`);
|
||||
}
|
||||
// Sort reverse so that conflicts content is added from last
|
||||
conflicts.sort((a, b) => b.firstIndex - a.firstIndex);
|
||||
|
||||
const tree = parseTree(mergeContent);
|
||||
for (const { firstIndex, local, remote } of conflicts) {
|
||||
const firstNode = findNodeAtLocation(tree, [firstIndex])!;
|
||||
const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length;
|
||||
let endLocalOffset = startLocalOffset;
|
||||
if (local) {
|
||||
const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!;
|
||||
endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length);
|
||||
}
|
||||
let remoteOffset = endLocalOffset;
|
||||
if (remote) {
|
||||
const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!;
|
||||
remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length);
|
||||
}
|
||||
mergeContent = mergeContent.substring(0, startLocalOffset)
|
||||
+ `${eol}<<<<<<< local`
|
||||
+ mergeContent.substring(startLocalOffset, endLocalOffset)
|
||||
+ `${eol}=======`
|
||||
+ mergeContent.substring(endLocalOffset, remoteOffset)
|
||||
+ `${eol}>>>>>>> remote`
|
||||
+ mergeContent.substring(remoteOffset);
|
||||
}
|
||||
|
||||
return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 };
|
||||
}
|
||||
|
||||
function compare(from: Map<string, IUserFriendlyKeybinding[]>, to: Map<string, IUserFriendlyKeybinding[]>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = keys(from);
|
||||
const toKeys = keys(to);
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1: IUserFriendlyKeybinding[] = from.get(key)!;
|
||||
const value2: IUserFriendlyKeybinding[] = to.get(key)!;
|
||||
if (!areSameKeybindings(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
function areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean {
|
||||
// Compare entries adding keybindings
|
||||
if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => isSameKeybinding(a, b))) {
|
||||
return false;
|
||||
}
|
||||
// Compare entries removing keybindings
|
||||
if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => isSameKeybinding(a, b))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean {
|
||||
if (a.command !== b.command) {
|
||||
return false;
|
||||
}
|
||||
if (a.key !== b.key) {
|
||||
return false;
|
||||
}
|
||||
const whenA = ContextKeyExpr.deserialize(a.when);
|
||||
const whenB = ContextKeyExpr.deserialize(b.when);
|
||||
if ((whenA && !whenB) || (!whenA && whenB)) {
|
||||
return false;
|
||||
}
|
||||
if (whenA && whenB && !whenA.equals(whenB)) {
|
||||
return false;
|
||||
}
|
||||
if (!objects.equals(a.args, b.args)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string {
|
||||
for (const keybinding of keybindings) {
|
||||
content = contentUtil.edit(content, eol, [-1], keybinding);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function removeKeybindings(content: string, eol: string, command: string): string {
|
||||
const keybindings = <IUserFriendlyKeybinding[]>parse(content);
|
||||
for (let index = keybindings.length - 1; index >= 0; index--) {
|
||||
if (keybindings[index].command === command || keybindings[index].command === `-${command}`) {
|
||||
content = contentUtil.edit(content, eol, [index], undefined);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string {
|
||||
const allKeybindings = <IUserFriendlyKeybinding[]>parse(content);
|
||||
const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`);
|
||||
// Remove all entries with this command
|
||||
for (let index = allKeybindings.length - 1; index >= 0; index--) {
|
||||
if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) {
|
||||
content = contentUtil.edit(content, eol, [index], undefined);
|
||||
}
|
||||
}
|
||||
// add all entries at the same location where the entry with this command was located.
|
||||
for (let index = keybindings.length - 1; index >= 0; index--) {
|
||||
content = contentUtil.edit(content, eol, [location], keybindings[index]);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse, ParseError } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -16,7 +16,6 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { mergeKeybindings } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
@@ -49,6 +48,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IKeybindingsMergeService private readonly keybindingsMergeService: IKeybindingsMergeService,
|
||||
) {
|
||||
super();
|
||||
this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json');
|
||||
@@ -220,7 +220,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
|| lastSyncData.content !== remoteContent // Remote has forwarded
|
||||
) {
|
||||
this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...');
|
||||
const result = mergeKeybindings(localContent, remoteContent, lastSyncData ? lastSyncData.content : null);
|
||||
const result = await this.keybindingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null);
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
hasLocalChanged = result.mergeContent !== localContent;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
export class KeybindingsMergeChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IKeybindingsMergeService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'merge': return this.service.merge(args[0], args[1], args[2]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class KeybindingsMergeChannelClient implements IKeybindingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
return this.channel.call('merge', [localContent, remoteContent, baseContent]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,19 +4,22 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { ISettingsMergeService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc';
|
||||
import { KeybindingsMergeChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc';
|
||||
|
||||
class UserDataSyncServicesContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@ISettingsMergeService settingsMergeService: ISettingsMergeService,
|
||||
@IKeybindingsMergeService keybindingsMergeService: IKeybindingsMergeService,
|
||||
@ISharedProcessService sharedProcessService: ISharedProcessService,
|
||||
) {
|
||||
sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService));
|
||||
sharedProcessService.registerChannel('keybindingsMerge', new KeybindingsMergeChannel(keybindingsMergeService));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json';
|
||||
import { values, keys } from 'vs/base/common/map';
|
||||
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import * as contentUtil from 'vs/platform/userDataSync/common/content';
|
||||
import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
export class KeybindingsMergeService implements IKeybindingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
public async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
const local = <IUserFriendlyKeybinding[]>parse(localContent);
|
||||
const remote = <IUserFriendlyKeybinding[]>parse(remoteContent);
|
||||
const base = baseContent ? <IUserFriendlyKeybinding[]>parse(baseContent) : null;
|
||||
|
||||
const byCommand = (keybindings: IUserFriendlyKeybinding[]) => {
|
||||
const map: Map<string, IUserFriendlyKeybinding[]> = new Map<string, IUserFriendlyKeybinding[]>();
|
||||
for (const keybinding of keybindings) {
|
||||
const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command;
|
||||
let value = map.get(command);
|
||||
if (!value) {
|
||||
value = [];
|
||||
map.set(command, value);
|
||||
}
|
||||
value.push(keybinding);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
const localByCommand = byCommand(local);
|
||||
const remoteByCommand = byCommand(remote);
|
||||
const baseByCommand = base ? byCommand(base) : null;
|
||||
|
||||
const localToRemote = this.compare(localByCommand, remoteByCommand);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { mergeContent: localContent, hasChanges: false, hasConflicts: false };
|
||||
}
|
||||
|
||||
const conflictCommands: Set<string> = new Set<string>();
|
||||
const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const eol = contentUtil.getEol(localContent);
|
||||
let mergeContent = localContent;
|
||||
|
||||
// Removed commands in Local
|
||||
for (const command of values(baseToLocal.removed)) {
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed commands in Remote
|
||||
for (const command of values(baseToRemote.removed)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
} else {
|
||||
// remove the command
|
||||
mergeContent = this.removeKeybindings(mergeContent, eol, command);
|
||||
}
|
||||
}
|
||||
|
||||
// Added commands in Local
|
||||
for (const command of values(baseToLocal.added)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in remote
|
||||
if (baseToRemote.added.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Added commands in remote
|
||||
for (const command of values(baseToRemote.added)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
} else {
|
||||
mergeContent = this.addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!);
|
||||
}
|
||||
}
|
||||
|
||||
// Updated commands in Local
|
||||
for (const command of values(baseToLocal.updated)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated commands in Remote
|
||||
for (const command of values(baseToRemote.updated)) {
|
||||
if (conflictCommands.has(command)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(command)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(command)) {
|
||||
conflictCommands.add(command);
|
||||
}
|
||||
} else {
|
||||
// update the command
|
||||
mergeContent = this.updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!);
|
||||
}
|
||||
}
|
||||
|
||||
const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = [];
|
||||
|
||||
for (const command of values(conflictCommands)) {
|
||||
const local = localByCommand.get(command);
|
||||
const remote = remoteByCommand.get(command);
|
||||
mergeContent = this.updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]);
|
||||
conflicts.push({ command, local, remote, firstIndex: -1 });
|
||||
}
|
||||
|
||||
const allKeybindings = <IUserFriendlyKeybinding[]>parse(mergeContent);
|
||||
for (const conflict of conflicts) {
|
||||
conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`);
|
||||
}
|
||||
// Sort reverse so that conflicts content is added from last
|
||||
conflicts.sort((a, b) => b.firstIndex - a.firstIndex);
|
||||
|
||||
const tree = parseTree(mergeContent);
|
||||
for (const { firstIndex, local, remote } of conflicts) {
|
||||
const firstNode = findNodeAtLocation(tree, [firstIndex])!;
|
||||
const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length;
|
||||
let endLocalOffset = startLocalOffset;
|
||||
if (local) {
|
||||
const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!;
|
||||
endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length);
|
||||
}
|
||||
let remoteOffset = endLocalOffset;
|
||||
if (remote) {
|
||||
const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!;
|
||||
remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length);
|
||||
}
|
||||
mergeContent = mergeContent.substring(0, startLocalOffset)
|
||||
+ `${eol}<<<<<<< local`
|
||||
+ mergeContent.substring(startLocalOffset, endLocalOffset)
|
||||
+ `${eol}=======`
|
||||
+ mergeContent.substring(endLocalOffset, remoteOffset)
|
||||
+ `${eol}>>>>>>> remote`
|
||||
+ mergeContent.substring(remoteOffset);
|
||||
}
|
||||
|
||||
return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 };
|
||||
}
|
||||
|
||||
private compare(from: Map<string, IUserFriendlyKeybinding[]>, to: Map<string, IUserFriendlyKeybinding[]>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = keys(from);
|
||||
const toKeys = keys(to);
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1: IUserFriendlyKeybinding[] = from.get(key)!;
|
||||
const value2: IUserFriendlyKeybinding[] = to.get(key)!;
|
||||
if (!this.areSameKeybindings(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
private areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean {
|
||||
// Compare entries adding keybindings
|
||||
if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => this.isSameKeybinding(a, b))) {
|
||||
return false;
|
||||
}
|
||||
// Compare entries removing keybindings
|
||||
if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => this.isSameKeybinding(a, b))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean {
|
||||
if (a.command !== b.command) {
|
||||
return false;
|
||||
}
|
||||
if (a.key !== b.key) {
|
||||
return false;
|
||||
}
|
||||
const whenA = ContextKeyExpr.deserialize(a.when);
|
||||
const whenB = ContextKeyExpr.deserialize(b.when);
|
||||
if ((whenA && !whenB) || (!whenA && whenB)) {
|
||||
return false;
|
||||
}
|
||||
if (whenA && whenB && !whenA.equals(whenB)) {
|
||||
return false;
|
||||
}
|
||||
if (!objects.equals(a.args, b.args)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string {
|
||||
for (const keybinding of keybindings) {
|
||||
content = contentUtil.edit(content, eol, [-1], keybinding);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private removeKeybindings(content: string, eol: string, command: string): string {
|
||||
const keybindings = <IUserFriendlyKeybinding[]>parse(content);
|
||||
for (let index = keybindings.length - 1; index >= 0; index--) {
|
||||
if (keybindings[index].command === command || keybindings[index].command === `-${command}`) {
|
||||
content = contentUtil.edit(content, eol, [index], undefined);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string {
|
||||
const allKeybindings = <IUserFriendlyKeybinding[]>parse(content);
|
||||
const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`);
|
||||
// Remove all entries with this command
|
||||
for (let index = allKeybindings.length - 1; index >= 0; index--) {
|
||||
if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) {
|
||||
content = contentUtil.edit(content, eol, [index], undefined);
|
||||
}
|
||||
}
|
||||
// add all entries at the same location where the entry with this command was located.
|
||||
for (let index = keybindings.length - 1; index >= 0; index--) {
|
||||
content = contentUtil.edit(content, eol, [location], keybindings[index]);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
+66
-61
@@ -4,29 +4,34 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { mergeKeybindings } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge';
|
||||
|
||||
let testObject: KeybindingsMergeService;
|
||||
|
||||
suiteSetup(() => testObject = workbenchInstantiationService().createInstance(KeybindingsMergeService));
|
||||
|
||||
suite('KeybindingsMerge - No Conflicts', () => {
|
||||
|
||||
test('merge when local and remote are same with one entry', () => {
|
||||
test('merge when local and remote are same with one entry', async () => {
|
||||
const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with similar when contexts', () => {
|
||||
test('merge when local and remote are same with similar when contexts', async () => {
|
||||
const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries', () => {
|
||||
test('merge when local and remote are same with multiple entries', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -37,13 +42,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } }
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with different base content', () => {
|
||||
test('merge when local and remote are same with different base content', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -58,13 +63,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } }
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, baseContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, baseContent);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries in different order', () => {
|
||||
test('merge when local and remote are same with multiple entries in different order', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -75,13 +80,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same when remove entry is in different order', () => {
|
||||
test('merge when local and remote are same when remove entry is in different order', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -92,13 +97,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote', () => {
|
||||
test('merge when a new entry is added to remote', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -108,13 +113,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote', () => {
|
||||
test('merge when multiple new entries are added to remote', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -125,13 +130,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } },
|
||||
{ key: 'cmd+d', command: 'c' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote from base and local has not changed', () => {
|
||||
test('merge when multiple new entries are added to remote from base and local has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -142,13 +147,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } },
|
||||
{ key: 'cmd+d', command: 'c' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, localContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, localContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from remote from base and local has not changed', () => {
|
||||
test('merge when an entry is removed from remote from base and local has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -158,13 +163,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, localContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, localContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when an entry (same command) is removed from remote from base and local has not changed', () => {
|
||||
test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -172,26 +177,26 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
const remoteContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, localContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, localContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in remote from base and local has not changed', () => {
|
||||
test('merge when an entry is updated in remote from base and local has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
]);
|
||||
const remoteContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, localContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, localContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when a command with multiple entries is updated from remote from base and local has not changed', () => {
|
||||
test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'shift+c', command: 'c' },
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
@@ -210,13 +215,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'cmd+d', command: 'a' },
|
||||
{ key: 'alt+d', command: 'b' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, localContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, localContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when remote has moved forwareded with multiple changes and local stays with base', () => {
|
||||
test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } },
|
||||
@@ -242,13 +247,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+f', command: 'f' },
|
||||
{ key: 'alt+d', command: '-f' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, localContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, localContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to local', () => {
|
||||
test('merge when a new entry is added to local', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -258,13 +263,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to local', () => {
|
||||
test('merge when multiple new entries are added to local', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -275,13 +280,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to local from base and remote is not changed', () => {
|
||||
test('merge when multiple new entries are added to local from base and remote is not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -292,13 +297,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, remoteContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, remoteContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from local from base and remote has not changed', () => {
|
||||
test('merge when an entry is removed from local from base and remote has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
@@ -308,13 +313,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
{ key: 'cmd+c', command: 'b', args: { text: '`' } },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, remoteContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, remoteContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry (with same command) is removed from local from base and remote has not changed', () => {
|
||||
test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
]);
|
||||
@@ -322,26 +327,26 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+d', command: '-a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, remoteContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, remoteContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in local from base and remote has not changed', () => {
|
||||
test('merge when an entry is updated in local from base and remote has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus' },
|
||||
]);
|
||||
const remoteContent = stringify([
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, remoteContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, remoteContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when a command with multiple entries is updated from local from base and remote has not changed', () => {
|
||||
test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'shift+c', command: 'c' },
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
@@ -354,13 +359,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+d', command: 'b' },
|
||||
{ key: 'cmd+d', command: 'a' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, remoteContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, remoteContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local has moved forwareded with multiple changes and remote stays with base', () => {
|
||||
test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'cmd+e', command: 'd' },
|
||||
@@ -386,13 +391,13 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'cmd+d', command: 'c', when: 'context1' },
|
||||
{ key: 'cmd+c', command: '-c' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, remoteContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, remoteContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when local and remote has moved forwareded with no conflicts', () => {
|
||||
test('merge when local and remote has moved forwareded with no conflicts', async () => {
|
||||
const baseContent = stringify([
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+c', command: '-a' },
|
||||
@@ -427,7 +432,7 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
{ key: 'alt+e', command: 'e' },
|
||||
{ key: 'alt+g', command: 'g', when: 'context2' },
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, baseContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, baseContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
@@ -437,10 +442,10 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
|
||||
suite('KeybindingsMerge - Conflicts', () => {
|
||||
|
||||
test('merge when local and remote with one entry but different value', () => {
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -461,7 +466,7 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
]`);
|
||||
});
|
||||
|
||||
test('merge when local and remote with different keybinding', () => {
|
||||
test('merge when local and remote with different keybinding', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' }
|
||||
@@ -470,7 +475,7 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' }
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -501,7 +506,7 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
]`);
|
||||
});
|
||||
|
||||
test('merge when local and remote has entries in different order', () => {
|
||||
test('merge when local and remote has entries in different order', async () => {
|
||||
const localContent = stringify([
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' },
|
||||
{ key: 'alt+a', command: 'a', when: 'editorTextFocus' }
|
||||
@@ -510,7 +515,7 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
{ key: 'alt+a', command: 'a', when: 'editorTextFocus' },
|
||||
{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }
|
||||
]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, null);
|
||||
const actual = await testObject.merge(localContent, remoteContent, null);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -541,11 +546,11 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
]`);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in local but updated in remote', () => {
|
||||
test('merge when the entry is removed in local but updated in remote', async () => {
|
||||
const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const localContent = stringify([]);
|
||||
const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, baseContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, baseContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -561,11 +566,11 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
]`);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in local but updated in remote and a new entry is added in local', () => {
|
||||
test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => {
|
||||
const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const localContent = stringify([{ key: 'alt+b', command: 'b' }]);
|
||||
const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, baseContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, baseContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -585,11 +590,11 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
]`);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in remote but updated in local', () => {
|
||||
test('merge when the entry is removed in remote but updated in local', async () => {
|
||||
const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const remoteContent = stringify([]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, baseContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, baseContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -605,11 +610,11 @@ suite('KeybindingsMerge - Conflicts', () => {
|
||||
]`);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', () => {
|
||||
test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => {
|
||||
const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]);
|
||||
const actual = mergeKeybindings(localContent, remoteContent, baseContent);
|
||||
const actual = await testObject.merge(localContent, remoteContent, baseContent);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
@@ -80,6 +80,7 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ
|
||||
import 'vs/workbench/services/notification/common/notificationService';
|
||||
import 'vs/workbench/services/extensions/common/staticExtensions';
|
||||
import 'vs/workbench/services/userDataSync/common/settingsMergeService';
|
||||
import 'vs/workbench/services/keybinding/common/keybindingsMerge';
|
||||
import 'vs/workbench/services/path/common/remotePathService';
|
||||
import 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
|
||||
Reference in New Issue
Block a user