diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 1492e7710c1..60766bcc4a5 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -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(); diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts deleted file mode 100644 index 4651bec300b..00000000000 --- a/src/vs/platform/userDataSync/common/keybindingsMerge.ts +++ /dev/null @@ -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 = parse(localContent); - const remote = parse(remoteContent); - const base = baseContent ? parse(baseContent) : null; - - const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { - const map: Map = new Map(); - 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 = new Set(); - const baseToLocal = baseByCommand ? compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemote = baseByCommand ? compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - 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 = 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, to: Map): { added: Set, removed: Set, updated: Set } { - 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()); - const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const updated: Set = new Set(); - - 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 = 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 = 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; -} diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 3af6ea8768c..4da5fadbe44 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -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; diff --git a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts new file mode 100644 index 00000000000..0bfa0de8f6a --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts @@ -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 { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + 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]); + } + +} diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 2b8fb68f933..9a07beb2623 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -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)); } } diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts new file mode 100644 index 00000000000..fda747c4861 --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -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 = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { + const map: Map = new Map(); + 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 = new Set(); + const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + 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 = 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, to: Map): { added: Set, removed: Set, updated: Set } { + 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()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + 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 = 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 = 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; + } +} diff --git a/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts similarity index 83% rename from src/vs/platform/userDataSync/test/keybindingsMerge.test.ts rename to src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index d1e8d85a41b..3df3c0293f6 100644 --- a/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -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, diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 5e499d4aa41..ebc12c69d14 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -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';