Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
e5687144
V
vscode
项目概览
xxadev
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
e5687144
编写于
12月 01, 2019
作者:
S
Sandeep Somavarapu
提交者:
GitHub
12月 01, 2019
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #85894 from microsoft/sandy081/syncKeybindings
Enable synchronising keybindings
上级
789416f4
6d951dd1
变更
17
展开全部
隐藏空白更改
内联
并排
Showing
17 changed file
with
1648 addition
and
12 deletion
+1648
-12
src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
.../code/electron-browser/sharedProcess/sharedProcessMain.ts
+3
-1
src/vs/platform/environment/common/environment.ts
src/vs/platform/environment/common/environment.ts
+1
-0
src/vs/platform/environment/node/environmentService.ts
src/vs/platform/environment/node/environmentService.ts
+3
-0
src/vs/platform/userDataSync/common/content.ts
src/vs/platform/userDataSync/common/content.ts
+64
-0
src/vs/platform/userDataSync/common/keybindingsMerge.ts
src/vs/platform/userDataSync/common/keybindingsMerge.ts
+364
-0
src/vs/platform/userDataSync/common/keybindingsSync.ts
src/vs/platform/userDataSync/common/keybindingsSync.ts
+348
-0
src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts
src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts
+38
-0
src/vs/platform/userDataSync/common/settingsSync.ts
src/vs/platform/userDataSync/common/settingsSync.ts
+4
-5
src/vs/platform/userDataSync/common/userDataSync.ts
src/vs/platform/userDataSync/common/userDataSync.ts
+24
-0
src/vs/platform/userDataSync/common/userDataSyncService.ts
src/vs/platform/userDataSync/common/userDataSyncService.ts
+7
-1
src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts
...latform/userDataSync/test/common/keybindingsMerge.test.ts
+721
-0
src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
...vs/workbench/contrib/userDataSync/browser/userDataSync.ts
+30
-3
src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts
...serDataSync/electron-browser/userDataSync.contribution.ts
+4
-1
src/vs/workbench/services/environment/browser/environmentService.ts
...kbench/services/environment/browser/environmentService.ts
+3
-0
src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts
...orkbench/services/userDataSync/common/keybindingsMerge.ts
+32
-0
src/vs/workbench/services/userDataSync/common/settingsMergeService.ts
...ench/services/userDataSync/common/settingsMergeService.ts
+1
-1
src/vs/workbench/workbench.common.main.ts
src/vs/workbench/workbench.common.main.ts
+1
-0
未找到文件。
src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
浏览文件 @
e5687144
...
...
@@ -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
,
IUserKeybindingsResolverService
}
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
{
UserKeybindingsResolverServiceClient
}
from
'
vs/platform/userDataSync/common/keybindingsSyncIpc
'
;
export
interface
ISharedProcessConfiguration
{
readonly
machineId
:
string
;
...
...
@@ -186,6 +187,7 @@ 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
));
services
.
set
(
IUserKeybindingsResolverService
,
new
UserKeybindingsResolverServiceClient
(
server
.
getChannel
(
'
userKeybindingsResolver
'
,
activeWindowRouter
)));
services
.
set
(
IUserDataSyncStoreService
,
new
SyncDescriptor
(
UserDataSyncStoreService
));
services
.
set
(
IUserDataSyncService
,
new
SyncDescriptor
(
UserDataSyncService
));
registerConfiguration
();
...
...
src/vs/platform/environment/common/environment.ts
浏览文件 @
e5687144
...
...
@@ -127,6 +127,7 @@ export interface IEnvironmentService extends IUserHomeProvider {
// sync resources
userDataSyncLogResource
:
URI
;
settingsSyncPreviewResource
:
URI
;
keybindingsSyncPreviewResource
:
URI
;
machineSettingsHome
:
URI
;
machineSettingsResource
:
URI
;
...
...
src/vs/platform/environment/node/environmentService.ts
浏览文件 @
e5687144
...
...
@@ -114,6 +114,9 @@ export class EnvironmentService implements IEnvironmentService {
@
memoize
get
settingsSyncPreviewResource
():
URI
{
return
resources
.
joinPath
(
this
.
userRoamingDataHome
,
'
.settings.json
'
);
}
@
memoize
get
keybindingsSyncPreviewResource
():
URI
{
return
resources
.
joinPath
(
this
.
userRoamingDataHome
,
'
.keybindings.json
'
);
}
@
memoize
get
userDataSyncLogResource
():
URI
{
return
URI
.
file
(
path
.
join
(
this
.
logsPath
,
'
userDataSync.log
'
));
}
...
...
src/vs/platform/userDataSync/common/content.ts
0 → 100644
浏览文件 @
e5687144
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
OperatingSystem
,
OS
}
from
'
vs/base/common/platform
'
;
import
{
JSONPath
}
from
'
vs/base/common/json
'
;
import
{
setProperty
}
from
'
vs/base/common/jsonEdit
'
;
export
function
edit
(
content
:
string
,
eol
:
string
,
originalPath
:
JSONPath
,
value
:
any
):
string
{
const
edit
=
setProperty
(
content
,
originalPath
,
value
,
{
tabSize
:
4
,
insertSpaces
:
false
,
eol
})[
0
];
if
(
edit
)
{
content
=
content
.
substring
(
0
,
edit
.
offset
)
+
edit
.
content
+
content
.
substring
(
edit
.
offset
+
edit
.
length
);
}
return
content
;
}
export
function
getLineStartOffset
(
content
:
string
,
eol
:
string
,
atOffset
:
number
):
number
{
let
lineStartingOffset
=
atOffset
;
while
(
lineStartingOffset
>=
0
)
{
if
(
content
.
charAt
(
lineStartingOffset
)
===
eol
.
charAt
(
eol
.
length
-
1
))
{
if
(
eol
.
length
===
1
)
{
return
lineStartingOffset
+
1
;
}
}
lineStartingOffset
--
;
if
(
eol
.
length
===
2
)
{
if
(
lineStartingOffset
>=
0
&&
content
.
charAt
(
lineStartingOffset
)
===
eol
.
charAt
(
0
))
{
return
lineStartingOffset
+
2
;
}
}
}
return
0
;
}
export
function
getLineEndOffset
(
content
:
string
,
eol
:
string
,
atOffset
:
number
):
number
{
let
lineEndOffset
=
atOffset
;
while
(
lineEndOffset
>=
0
)
{
if
(
content
.
charAt
(
lineEndOffset
)
===
eol
.
charAt
(
eol
.
length
-
1
))
{
if
(
eol
.
length
===
1
)
{
return
lineEndOffset
;
}
}
lineEndOffset
++
;
if
(
eol
.
length
===
2
)
{
if
(
lineEndOffset
>=
0
&&
content
.
charAt
(
lineEndOffset
)
===
eol
.
charAt
(
1
))
{
return
lineEndOffset
;
}
}
}
return
content
.
length
-
1
;
}
export
function
getEol
(
content
:
string
):
string
{
if
(
content
.
indexOf
(
'
\r\n
'
)
!==
-
1
)
{
return
'
\r\n
'
;
}
if
(
content
.
indexOf
(
'
\n
'
)
!==
-
1
)
{
return
'
\n
'
;
}
return
OS
===
OperatingSystem
.
Linux
||
OS
===
OperatingSystem
.
Macintosh
?
'
\n
'
:
'
\r\n
'
;
}
src/vs/platform/userDataSync/common/keybindingsMerge.ts
0 → 100644
浏览文件 @
e5687144
/*---------------------------------------------------------------------------------------------
* 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
}
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
{
IStringDictionary
}
from
'
vs/base/common/collections
'
;
interface
ICompareResult
{
added
:
Set
<
string
>
;
removed
:
Set
<
string
>
;
updated
:
Set
<
string
>
;
}
interface
IMergeResult
{
hasLocalForwarded
:
boolean
;
hasRemoteForwarded
:
boolean
;
added
:
Set
<
string
>
;
removed
:
Set
<
string
>
;
updated
:
Set
<
string
>
;
conflicts
:
Set
<
string
>
;
}
export
function
merge
(
localContent
:
string
,
remoteContent
:
string
,
baseContent
:
string
|
null
,
normalizedKeys
:
IStringDictionary
<
string
>
):
{
mergeContent
:
string
,
hasChanges
:
boolean
,
hasConflicts
:
boolean
}
{
const
local
=
<
IUserFriendlyKeybinding
[]
>
parse
(
localContent
);
const
remote
=
<
IUserFriendlyKeybinding
[]
>
parse
(
remoteContent
);
const
base
=
baseContent
?
<
IUserFriendlyKeybinding
[]
>
parse
(
baseContent
)
:
null
;
let
keybindingsMergeResult
=
computeMergeResultByKeybinding
(
local
,
remote
,
base
,
normalizedKeys
);
if
(
!
keybindingsMergeResult
.
hasLocalForwarded
&&
!
keybindingsMergeResult
.
hasRemoteForwarded
)
{
// No changes found between local and remote.
return
{
mergeContent
:
localContent
,
hasChanges
:
false
,
hasConflicts
:
false
};
}
if
(
!
keybindingsMergeResult
.
hasLocalForwarded
&&
keybindingsMergeResult
.
hasRemoteForwarded
)
{
return
{
mergeContent
:
remoteContent
,
hasChanges
:
true
,
hasConflicts
:
false
};
}
if
(
keybindingsMergeResult
.
hasLocalForwarded
&&
!
keybindingsMergeResult
.
hasRemoteForwarded
)
{
// Local has moved forward and remote has not. Return local.
return
{
mergeContent
:
localContent
,
hasChanges
:
true
,
hasConflicts
:
false
};
}
// Both local and remote has moved forward.
const
localByCommand
=
byCommand
(
local
);
const
remoteByCommand
=
byCommand
(
remote
);
const
baseByCommand
=
base
?
byCommand
(
base
)
:
null
;
const
localToRemoteByCommand
=
compareByCommand
(
localByCommand
,
remoteByCommand
,
normalizedKeys
);
const
baseToLocalByCommand
=
baseByCommand
?
compareByCommand
(
baseByCommand
,
localByCommand
,
normalizedKeys
)
:
{
added
:
keys
(
localByCommand
).
reduce
((
r
,
k
)
=>
{
r
.
add
(
k
);
return
r
;
},
new
Set
<
string
>
()),
removed
:
new
Set
<
string
>
(),
updated
:
new
Set
<
string
>
()
};
const
baseToRemoteByCommand
=
baseByCommand
?
compareByCommand
(
baseByCommand
,
remoteByCommand
,
normalizedKeys
)
:
{
added
:
keys
(
remoteByCommand
).
reduce
((
r
,
k
)
=>
{
r
.
add
(
k
);
return
r
;
},
new
Set
<
string
>
()),
removed
:
new
Set
<
string
>
(),
updated
:
new
Set
<
string
>
()
};
const
commandsMergeResult
=
computeMergeResult
(
localToRemoteByCommand
,
baseToLocalByCommand
,
baseToRemoteByCommand
);
const
eol
=
contentUtil
.
getEol
(
localContent
);
let
mergeContent
=
localContent
;
// Removed commands in Remote
for
(
const
command
of
values
(
commandsMergeResult
.
removed
))
{
if
(
commandsMergeResult
.
conflicts
.
has
(
command
))
{
continue
;
}
mergeContent
=
removeKeybindings
(
mergeContent
,
eol
,
command
);
}
// Added commands in remote
for
(
const
command
of
values
(
commandsMergeResult
.
added
))
{
if
(
commandsMergeResult
.
conflicts
.
has
(
command
))
{
continue
;
}
const
keybindings
=
remoteByCommand
.
get
(
command
)
!
;
// Ignore negated commands
if
(
keybindings
.
some
(
keybinding
=>
keybinding
.
command
!==
`-
${
command
}
`
&&
keybindingsMergeResult
.
conflicts
.
has
(
normalizedKeys
[
keybinding
.
key
])))
{
commandsMergeResult
.
conflicts
.
add
(
command
);
continue
;
}
mergeContent
=
addKeybindings
(
mergeContent
,
eol
,
keybindings
);
}
// Updated commands in Remote
for
(
const
command
of
values
(
commandsMergeResult
.
updated
))
{
if
(
commandsMergeResult
.
conflicts
.
has
(
command
))
{
continue
;
}
const
keybindings
=
remoteByCommand
.
get
(
command
)
!
;
// Ignore negated commands
if
(
keybindings
.
some
(
keybinding
=>
keybinding
.
command
!==
`-
${
command
}
`
&&
keybindingsMergeResult
.
conflicts
.
has
(
normalizedKeys
[
keybinding
.
key
])))
{
commandsMergeResult
.
conflicts
.
add
(
command
);
continue
;
}
mergeContent
=
updateKeybindings
(
mergeContent
,
eol
,
command
,
keybindings
);
}
const
hasConflicts
=
commandsMergeResult
.
conflicts
.
size
>
0
;
if
(
hasConflicts
)
{
mergeContent
=
`<<<<<<< local
${
eol
}
`
+
mergeContent
+
`
${
eol
}
=======
${
eol
}
`
+
remoteContent
+
`
${
eol
}
>>>>>>> remote`
;
}
return
{
mergeContent
,
hasChanges
:
true
,
hasConflicts
};
}
function
computeMergeResult
(
localToRemote
:
ICompareResult
,
baseToLocal
:
ICompareResult
,
baseToRemote
:
ICompareResult
):
{
added
:
Set
<
string
>
,
removed
:
Set
<
string
>
,
updated
:
Set
<
string
>
,
conflicts
:
Set
<
string
>
}
{
const
added
:
Set
<
string
>
=
new
Set
<
string
>
();
const
removed
:
Set
<
string
>
=
new
Set
<
string
>
();
const
updated
:
Set
<
string
>
=
new
Set
<
string
>
();
const
conflicts
:
Set
<
string
>
=
new
Set
<
string
>
();
// Removed keys in Local
for
(
const
key
of
values
(
baseToLocal
.
removed
))
{
// Got updated in remote
if
(
baseToRemote
.
updated
.
has
(
key
))
{
conflicts
.
add
(
key
);
}
}
// Removed keys in Remote
for
(
const
key
of
values
(
baseToRemote
.
removed
))
{
if
(
conflicts
.
has
(
key
))
{
continue
;
}
// Got updated in local
if
(
baseToLocal
.
updated
.
has
(
key
))
{
conflicts
.
add
(
key
);
}
else
{
// remove the key
removed
.
add
(
key
);
}
}
// Added keys in Local
for
(
const
key
of
values
(
baseToLocal
.
added
))
{
if
(
conflicts
.
has
(
key
))
{
continue
;
}
// Got added in remote
if
(
baseToRemote
.
added
.
has
(
key
))
{
// Has different value
if
(
localToRemote
.
updated
.
has
(
key
))
{
conflicts
.
add
(
key
);
}
}
}
// Added keys in remote
for
(
const
key
of
values
(
baseToRemote
.
added
))
{
if
(
conflicts
.
has
(
key
))
{
continue
;
}
// Got added in local
if
(
baseToLocal
.
added
.
has
(
key
))
{
// Has different value
if
(
localToRemote
.
updated
.
has
(
key
))
{
conflicts
.
add
(
key
);
}
}
else
{
added
.
add
(
key
);
}
}
// Updated keys in Local
for
(
const
key
of
values
(
baseToLocal
.
updated
))
{
if
(
conflicts
.
has
(
key
))
{
continue
;
}
// Got updated in remote
if
(
baseToRemote
.
updated
.
has
(
key
))
{
// Has different value
if
(
localToRemote
.
updated
.
has
(
key
))
{
conflicts
.
add
(
key
);
}
}
}
// Updated keys in Remote
for
(
const
key
of
values
(
baseToRemote
.
updated
))
{
if
(
conflicts
.
has
(
key
))
{
continue
;
}
// Got updated in local
if
(
baseToLocal
.
updated
.
has
(
key
))
{
// Has different value
if
(
localToRemote
.
updated
.
has
(
key
))
{
conflicts
.
add
(
key
);
}
}
else
{
// updated key
updated
.
add
(
key
);
}
}
return
{
added
,
removed
,
updated
,
conflicts
};
}
function
computeMergeResultByKeybinding
(
local
:
IUserFriendlyKeybinding
[],
remote
:
IUserFriendlyKeybinding
[],
base
:
IUserFriendlyKeybinding
[]
|
null
,
normalizedKeys
:
IStringDictionary
<
string
>
):
IMergeResult
{
const
empty
=
new
Set
<
string
>
();
const
localByKeybinding
=
byKeybinding
(
local
,
normalizedKeys
);
const
remoteByKeybinding
=
byKeybinding
(
remote
,
normalizedKeys
);
const
baseByKeybinding
=
base
?
byKeybinding
(
base
,
normalizedKeys
)
:
null
;
const
localToRemoteByKeybinding
=
compareByKeybinding
(
localByKeybinding
,
remoteByKeybinding
);
if
(
localToRemoteByKeybinding
.
added
.
size
===
0
&&
localToRemoteByKeybinding
.
removed
.
size
===
0
&&
localToRemoteByKeybinding
.
updated
.
size
===
0
)
{
return
{
hasLocalForwarded
:
false
,
hasRemoteForwarded
:
false
,
added
:
empty
,
removed
:
empty
,
updated
:
empty
,
conflicts
:
empty
};
}
const
baseToLocalByKeybinding
=
baseByKeybinding
?
compareByKeybinding
(
baseByKeybinding
,
localByKeybinding
)
:
{
added
:
keys
(
localByKeybinding
).
reduce
((
r
,
k
)
=>
{
r
.
add
(
k
);
return
r
;
},
new
Set
<
string
>
()),
removed
:
new
Set
<
string
>
(),
updated
:
new
Set
<
string
>
()
};
if
(
baseToLocalByKeybinding
.
added
.
size
===
0
&&
baseToLocalByKeybinding
.
removed
.
size
===
0
&&
baseToLocalByKeybinding
.
updated
.
size
===
0
)
{
// Remote has moved forward and local has not.
return
{
hasLocalForwarded
:
false
,
hasRemoteForwarded
:
true
,
added
:
empty
,
removed
:
empty
,
updated
:
empty
,
conflicts
:
empty
};
}
const
baseToRemoteByKeybinding
=
baseByKeybinding
?
compareByKeybinding
(
baseByKeybinding
,
remoteByKeybinding
)
:
{
added
:
keys
(
remoteByKeybinding
).
reduce
((
r
,
k
)
=>
{
r
.
add
(
k
);
return
r
;
},
new
Set
<
string
>
()),
removed
:
new
Set
<
string
>
(),
updated
:
new
Set
<
string
>
()
};
if
(
baseToRemoteByKeybinding
.
added
.
size
===
0
&&
baseToRemoteByKeybinding
.
removed
.
size
===
0
&&
baseToRemoteByKeybinding
.
updated
.
size
===
0
)
{
return
{
hasLocalForwarded
:
true
,
hasRemoteForwarded
:
false
,
added
:
empty
,
removed
:
empty
,
updated
:
empty
,
conflicts
:
empty
};
}
const
{
added
,
removed
,
updated
,
conflicts
}
=
computeMergeResult
(
localToRemoteByKeybinding
,
baseToLocalByKeybinding
,
baseToRemoteByKeybinding
);
return
{
hasLocalForwarded
:
true
,
hasRemoteForwarded
:
true
,
added
,
removed
,
updated
,
conflicts
};
}
function
byKeybinding
(
keybindings
:
IUserFriendlyKeybinding
[],
keys
:
IStringDictionary
<
string
>
)
{
const
map
:
Map
<
string
,
IUserFriendlyKeybinding
[]
>
=
new
Map
<
string
,
IUserFriendlyKeybinding
[]
>
();
for
(
const
keybinding
of
keybindings
)
{
const
key
=
keys
[
keybinding
.
key
];
let
value
=
map
.
get
(
key
);
if
(
!
value
)
{
value
=
[];
map
.
set
(
key
,
value
);
}
value
.
push
(
keybinding
);
}
return
map
;
}
function
byCommand
(
keybindings
:
IUserFriendlyKeybinding
[]):
Map
<
string
,
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
;
}
function
compareByKeybinding
(
from
:
Map
<
string
,
IUserFriendlyKeybinding
[]
>
,
to
:
Map
<
string
,
IUserFriendlyKeybinding
[]
>
):
ICompareResult
{
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
)
!
.
map
(
keybinding
=>
({
...
keybinding
,
...{
key
}
}));
const
value2
:
IUserFriendlyKeybinding
[]
=
to
.
get
(
key
)
!
.
map
(
keybinding
=>
({
...
keybinding
,
...{
key
}
}));
if
(
!
equals
(
value1
,
value2
,
(
a
,
b
)
=>
isSameKeybinding
(
a
,
b
)))
{
updated
.
add
(
key
);
}
}
return
{
added
,
removed
,
updated
};
}
function
compareByCommand
(
from
:
Map
<
string
,
IUserFriendlyKeybinding
[]
>
,
to
:
Map
<
string
,
IUserFriendlyKeybinding
[]
>
,
normalizedKeys
:
IStringDictionary
<
string
>
):
ICompareResult
{
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
)
!
.
map
(
keybinding
=>
({
...
keybinding
,
...{
key
:
normalizedKeys
[
keybinding
.
key
]
}
}));
const
value2
:
IUserFriendlyKeybinding
[]
=
to
.
get
(
key
)
!
.
map
(
keybinding
=>
({
...
keybinding
,
...{
key
:
normalizedKeys
[
keybinding
.
key
]
}
}));
if
(
!
areSameKeybindingsWithSameCommand
(
value1
,
value2
))
{
updated
.
add
(
key
);
}
}
return
{
added
,
removed
,
updated
};
}
function
areSameKeybindingsWithSameCommand
(
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
addKeybindings
(
content
:
string
,
eol
:
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
updateKeybindings
(
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
;
}
src/vs/platform/userDataSync/common/keybindingsSync.ts
0 → 100644
浏览文件 @
e5687144
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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
,
IUserKeybindingsResolverService
}
from
'
vs/platform/userDataSync/common/userDataSync
'
;
import
{
merge
}
from
'
vs/platform/userDataSync/common/keybindingsMerge
'
;
import
{
VSBuffer
}
from
'
vs/base/common/buffer
'
;
import
{
parse
,
ParseError
}
from
'
vs/base/common/json
'
;
import
{
localize
}
from
'
vs/nls
'
;
import
{
Emitter
,
Event
}
from
'
vs/base/common/event
'
;
import
{
CancelablePromise
,
createCancelablePromise
,
ThrottledDelayer
}
from
'
vs/base/common/async
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
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
{
OS
,
OperatingSystem
}
from
'
vs/base/common/platform
'
;
import
{
isUndefined
}
from
'
vs/base/common/types
'
;
interface
ISyncContent
{
mac
?:
string
;
linux
?:
string
;
windows
?:
string
;
all
?:
string
;
}
interface
ISyncPreviewResult
{
readonly
fileContent
:
IFileContent
|
null
;
readonly
remoteUserData
:
IUserData
;
readonly
hasLocalChanged
:
boolean
;
readonly
hasRemoteChanged
:
boolean
;
readonly
hasConflicts
:
boolean
;
}
export
class
KeybindingsSynchroniser
extends
Disposable
implements
ISynchroniser
{
private
static
EXTERNAL_USER_DATA_KEYBINDINGS_KEY
:
string
=
'
keybindings
'
;
private
syncPreviewResultPromise
:
CancelablePromise
<
ISyncPreviewResult
>
|
null
=
null
;
private
_status
:
SyncStatus
=
SyncStatus
.
Idle
;
get
status
():
SyncStatus
{
return
this
.
_status
;
}
private
_onDidChangStatus
:
Emitter
<
SyncStatus
>
=
this
.
_register
(
new
Emitter
<
SyncStatus
>
());
readonly
onDidChangeStatus
:
Event
<
SyncStatus
>
=
this
.
_onDidChangStatus
.
event
;
private
readonly
throttledDelayer
:
ThrottledDelayer
<
void
>
;
private
_onDidChangeLocal
:
Emitter
<
void
>
=
this
.
_register
(
new
Emitter
<
void
>
());
readonly
onDidChangeLocal
:
Event
<
void
>
=
this
.
_onDidChangeLocal
.
event
;
private
readonly
lastSyncKeybindingsResource
:
URI
;
constructor
(
@
IUserDataSyncStoreService
private
readonly
userDataSyncStoreService
:
IUserDataSyncStoreService
,
@
IUserDataSyncLogService
private
readonly
logService
:
IUserDataSyncLogService
,
@
IConfigurationService
private
readonly
configurationService
:
IConfigurationService
,
@
IFileService
private
readonly
fileService
:
IFileService
,
@
IEnvironmentService
private
readonly
environmentService
:
IEnvironmentService
,
@
IUserKeybindingsResolverService
private
readonly
userKeybindingsResolverService
:
IUserKeybindingsResolverService
,
)
{
super
();
this
.
lastSyncKeybindingsResource
=
joinPath
(
this
.
environmentService
.
userRoamingDataHome
,
'
.lastSyncKeybindings.json
'
);
this
.
throttledDelayer
=
this
.
_register
(
new
ThrottledDelayer
<
void
>
(
500
));
this
.
_register
(
this
.
fileService
.
watch
(
this
.
environmentService
.
keybindingsResource
));
this
.
_register
(
Event
.
filter
(
this
.
fileService
.
onFileChanges
,
e
=>
e
.
contains
(
this
.
environmentService
.
keybindingsResource
))(()
=>
this
.
throttledDelayer
.
trigger
(()
=>
this
.
onDidChangeKeybindings
())));
}
private
async
onDidChangeKeybindings
():
Promise
<
void
>
{
const
localFileContent
=
await
this
.
getLocalContent
();
const
lastSyncData
=
await
this
.
getLastSyncUserData
();
if
(
localFileContent
&&
lastSyncData
)
{
if
(
localFileContent
.
value
.
toString
()
!==
lastSyncData
.
content
)
{
this
.
_onDidChangeLocal
.
fire
();
return
;
}
}
if
(
!
localFileContent
||
!
lastSyncData
)
{
this
.
_onDidChangeLocal
.
fire
();
return
;
}
}
private
setStatus
(
status
:
SyncStatus
):
void
{
if
(
this
.
_status
!==
status
)
{
this
.
_status
=
status
;
this
.
_onDidChangStatus
.
fire
(
status
);
}
}
async
sync
(
_continue
?:
boolean
):
Promise
<
boolean
>
{
if
(
!
this
.
configurationService
.
getValue
<
boolean
>
(
'
sync.enableKeybindings
'
))
{
this
.
logService
.
trace
(
'
Keybindings: Skipping synchronizing keybindings as it is disabled.
'
);
return
false
;
}
if
(
_continue
)
{
this
.
logService
.
info
(
'
Keybindings: Resumed synchronizing keybindings
'
);
return
this
.
continueSync
();
}
if
(
this
.
status
!==
SyncStatus
.
Idle
)
{
this
.
logService
.
trace
(
'
Keybindings: Skipping synchronizing keybindings as it is running already.
'
);
return
false
;
}
this
.
logService
.
trace
(
'
Keybindings: Started synchronizing keybindings...
'
);
this
.
setStatus
(
SyncStatus
.
Syncing
);
try
{
const
result
=
await
this
.
getPreview
();
if
(
result
.
hasConflicts
)
{
this
.
logService
.
info
(
'
Keybindings: Detected conflicts while synchronizing keybindings.
'
);
this
.
setStatus
(
SyncStatus
.
HasConflicts
);
return
false
;
}
await
this
.
apply
();
return
true
;
}
catch
(
e
)
{
this
.
syncPreviewResultPromise
=
null
;
this
.
setStatus
(
SyncStatus
.
Idle
);
if
(
e
instanceof
UserDataSyncStoreError
&&
e
.
code
===
UserDataSyncStoreErrorCode
.
Rejected
)
{
// Rejected as there is a new remote version. Syncing again,
this
.
logService
.
info
(
'
Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...
'
);
return
this
.
sync
();
}
if
(
e
instanceof
FileSystemProviderError
&&
e
.
code
===
FileSystemProviderErrorCode
.
FileExists
)
{
// Rejected as there is a new local version. Syncing again.
this
.
logService
.
info
(
'
Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...
'
);
return
this
.
sync
();
}
throw
e
;
}
}
stop
():
void
{
if
(
this
.
syncPreviewResultPromise
)
{
this
.
syncPreviewResultPromise
.
cancel
();
this
.
syncPreviewResultPromise
=
null
;
this
.
logService
.
info
(
'
Keybindings: Stopped synchronizing keybindings.
'
);
}
this
.
fileService
.
del
(
this
.
environmentService
.
keybindingsSyncPreviewResource
);
this
.
setStatus
(
SyncStatus
.
Idle
);
}
private
async
continueSync
():
Promise
<
boolean
>
{
if
(
this
.
status
!==
SyncStatus
.
HasConflicts
)
{
return
false
;
}
await
this
.
apply
();
return
true
;
}
private
async
apply
():
Promise
<
void
>
{
if
(
!
this
.
syncPreviewResultPromise
)
{
return
;
}
if
(
await
this
.
fileService
.
exists
(
this
.
environmentService
.
keybindingsSyncPreviewResource
))
{
const
keybindingsPreivew
=
await
this
.
fileService
.
readFile
(
this
.
environmentService
.
keybindingsSyncPreviewResource
);
const
content
=
keybindingsPreivew
.
value
.
toString
();
if
(
this
.
hasErrors
(
content
))
{
const
error
=
new
Error
(
localize
(
'
errorInvalidKeybindings
'
,
"
Unable to sync keybindings. Please resolve conflicts without any errors/warnings and try again.
"
));
this
.
logService
.
error
(
error
);
throw
error
;
}
let
{
fileContent
,
remoteUserData
,
hasLocalChanged
,
hasRemoteChanged
}
=
await
this
.
syncPreviewResultPromise
;
if
(
!
hasLocalChanged
&&
!
hasRemoteChanged
)
{
this
.
logService
.
trace
(
'
Keybindings: No changes found during synchronizing keybindings.
'
);
}
if
(
hasLocalChanged
)
{
this
.
logService
.
info
(
'
Keybindings: Updating local keybindings
'
);
await
this
.
updateLocalContent
(
content
,
fileContent
);
}
if
(
hasRemoteChanged
)
{
this
.
logService
.
info
(
'
Keybindings: Updating remote keybindings
'
);
const
remoteContents
=
this
.
updateSyncContent
(
content
,
remoteUserData
.
content
);
const
ref
=
await
this
.
updateRemoteUserData
(
remoteContents
,
remoteUserData
.
ref
);
remoteUserData
=
{
ref
,
content
:
remoteContents
};
}
if
(
remoteUserData
.
content
)
{
this
.
logService
.
info
(
'
Keybindings: Updating last synchronised keybindings
'
);
const
lastSyncContent
=
this
.
updateSyncContent
(
content
,
null
);
await
this
.
updateLastSyncUserData
({
ref
:
remoteUserData
.
ref
,
content
:
lastSyncContent
});
}
// Delete the preview
await
this
.
fileService
.
del
(
this
.
environmentService
.
keybindingsSyncPreviewResource
);
}
else
{
this
.
logService
.
trace
(
'
Keybindings: No changes found during synchronizing keybindings.
'
);
}
this
.
logService
.
trace
(
'
Keybindings: Finised synchronizing keybindings.
'
);
this
.
syncPreviewResultPromise
=
null
;
this
.
setStatus
(
SyncStatus
.
Idle
);
}
private
hasErrors
(
content
:
string
):
boolean
{
const
parseErrors
:
ParseError
[]
=
[];
parse
(
content
,
parseErrors
,
{
allowEmptyContent
:
true
,
allowTrailingComma
:
true
});
return
parseErrors
.
length
>
0
;
}
private
getPreview
():
Promise
<
ISyncPreviewResult
>
{
if
(
!
this
.
syncPreviewResultPromise
)
{
this
.
syncPreviewResultPromise
=
createCancelablePromise
(
token
=>
this
.
generatePreview
(
token
));
}
return
this
.
syncPreviewResultPromise
;
}
private
async
generatePreview
(
token
:
CancellationToken
):
Promise
<
ISyncPreviewResult
>
{
const
lastSyncData
=
await
this
.
getLastSyncUserData
();
const
lastSyncContent
=
lastSyncData
&&
lastSyncData
.
content
?
this
.
getKeybindingsContentFromSyncContent
(
lastSyncData
.
content
)
:
null
;
const
remoteUserData
=
await
this
.
getRemoteUserData
(
lastSyncData
);
const
remoteContent
=
remoteUserData
.
content
?
this
.
getKeybindingsContentFromSyncContent
(
remoteUserData
.
content
)
:
null
;
// Get file content last to get the latest
const
fileContent
=
await
this
.
getLocalContent
();
let
hasLocalChanged
:
boolean
=
false
;
let
hasRemoteChanged
:
boolean
=
false
;
let
hasConflicts
:
boolean
=
false
;
let
previewContent
=
null
;
if
(
remoteContent
)
{
const
localContent
:
string
=
fileContent
?
fileContent
.
value
.
toString
()
:
'
[]
'
;
if
(
this
.
hasErrors
(
localContent
))
{
this
.
logService
.
error
(
'
Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.
'
);
return
{
fileContent
,
remoteUserData
,
hasLocalChanged
,
hasRemoteChanged
,
hasConflicts
};
}
if
(
!
lastSyncContent
// First time sync
||
lastSyncContent
!==
localContent
// Local has forwarded
||
lastSyncContent
!==
remoteContent
// Remote has forwarded
)
{
this
.
logService
.
trace
(
'
Keybindings: Merging remote keybindings with local keybindings...
'
);
const
keys
=
await
this
.
userKeybindingsResolverService
.
resolveUserKeybindings
(
localContent
,
remoteContent
,
lastSyncContent
);
const
result
=
merge
(
localContent
,
remoteContent
,
lastSyncContent
,
keys
);
// Sync only if there are changes
if
(
result
.
hasChanges
)
{
hasLocalChanged
=
result
.
mergeContent
!==
localContent
;
hasRemoteChanged
=
result
.
mergeContent
!==
remoteContent
;
hasConflicts
=
result
.
hasConflicts
;
previewContent
=
result
.
mergeContent
;
}
}
}
// First time syncing to remote
else
if
(
fileContent
)
{
this
.
logService
.
info
(
'
Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.
'
);
hasRemoteChanged
=
true
;
previewContent
=
fileContent
.
value
.
toString
();
}
if
(
previewContent
&&
!
token
.
isCancellationRequested
)
{
await
this
.
fileService
.
writeFile
(
this
.
environmentService
.
keybindingsSyncPreviewResource
,
VSBuffer
.
fromString
(
previewContent
));
}
return
{
fileContent
,
remoteUserData
,
hasLocalChanged
,
hasRemoteChanged
,
hasConflicts
};
}
private
async
getLocalContent
():
Promise
<
IFileContent
|
null
>
{
try
{
return
await
this
.
fileService
.
readFile
(
this
.
environmentService
.
keybindingsResource
);
}
catch
(
error
)
{
return
null
;
}
}
private
async
updateLocalContent
(
newContent
:
string
,
oldContent
:
IFileContent
|
null
):
Promise
<
void
>
{
if
(
oldContent
)
{
// file exists already
await
this
.
fileService
.
writeFile
(
this
.
environmentService
.
keybindingsResource
,
VSBuffer
.
fromString
(
newContent
),
oldContent
);
}
else
{
// file does not exist
await
this
.
fileService
.
createFile
(
this
.
environmentService
.
keybindingsResource
,
VSBuffer
.
fromString
(
newContent
),
{
overwrite
:
false
});
}
}
private
async
getLastSyncUserData
():
Promise
<
IUserData
|
null
>
{
try
{
const
content
=
await
this
.
fileService
.
readFile
(
this
.
lastSyncKeybindingsResource
);
return
JSON
.
parse
(
content
.
value
.
toString
());
}
catch
(
error
)
{
return
null
;
}
}
private
async
updateLastSyncUserData
(
remoteUserData
:
IUserData
):
Promise
<
void
>
{
await
this
.
fileService
.
writeFile
(
this
.
lastSyncKeybindingsResource
,
VSBuffer
.
fromString
(
JSON
.
stringify
(
remoteUserData
)));
}
private
async
getRemoteUserData
(
lastSyncData
:
IUserData
|
null
):
Promise
<
IUserData
>
{
return
this
.
userDataSyncStoreService
.
read
(
KeybindingsSynchroniser
.
EXTERNAL_USER_DATA_KEYBINDINGS_KEY
,
lastSyncData
);
}
private
async
updateRemoteUserData
(
content
:
string
,
ref
:
string
|
null
):
Promise
<
string
>
{
return
this
.
userDataSyncStoreService
.
write
(
KeybindingsSynchroniser
.
EXTERNAL_USER_DATA_KEYBINDINGS_KEY
,
content
,
ref
);
}
private
getKeybindingsContentFromSyncContent
(
syncContent
:
string
):
string
|
null
{
try
{
const
parsed
=
<
ISyncContent
>
JSON
.
parse
(
syncContent
);
if
(
!
this
.
configurationService
.
getValue
<
boolean
>
(
'
sync.keybindingsPerPlatform
'
))
{
return
isUndefined
(
parsed
.
all
)
?
null
:
parsed
.
all
;
}
switch
(
OS
)
{
case
OperatingSystem
.
Macintosh
:
return
isUndefined
(
parsed
.
mac
)
?
null
:
parsed
.
mac
;
case
OperatingSystem
.
Linux
:
return
isUndefined
(
parsed
.
linux
)
?
null
:
parsed
.
linux
;
case
OperatingSystem
.
Windows
:
return
isUndefined
(
parsed
.
windows
)
?
null
:
parsed
.
windows
;
}
}
catch
(
e
)
{
this
.
logService
.
error
(
e
);
return
null
;
}
}
private
updateSyncContent
(
keybindingsContent
:
string
,
syncContent
:
string
|
null
):
string
{
let
parsed
:
ISyncContent
=
{};
try
{
parsed
=
JSON
.
parse
(
syncContent
||
'
{}
'
);
}
catch
(
e
)
{
this
.
logService
.
error
(
e
);
}
if
(
!
this
.
configurationService
.
getValue
<
boolean
>
(
'
sync.keybindingsPerPlatform
'
))
{
parsed
.
all
=
keybindingsContent
;
}
else
{
delete
parsed
.
all
;
}
switch
(
OS
)
{
case
OperatingSystem
.
Macintosh
:
parsed
.
mac
=
keybindingsContent
;
break
;
case
OperatingSystem
.
Linux
:
parsed
.
linux
=
keybindingsContent
;
break
;
case
OperatingSystem
.
Windows
:
parsed
.
windows
=
keybindingsContent
;
break
;
}
return
JSON
.
stringify
(
parsed
);
}
}
src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts
0 → 100644
浏览文件 @
e5687144
/*---------------------------------------------------------------------------------------------
* 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
{
IUserKeybindingsResolverService
}
from
'
vs/platform/userDataSync/common/userDataSync
'
;
import
{
IStringDictionary
}
from
'
vs/base/common/collections
'
;
export
class
UserKeybindingsResolverServiceChannel
implements
IServerChannel
{
constructor
(
private
readonly
service
:
IUserKeybindingsResolverService
)
{
}
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
'
resolveUserKeybindings
'
:
return
this
.
service
.
resolveUserKeybindings
(
args
[
0
],
args
[
1
],
args
[
2
]);
}
throw
new
Error
(
'
Invalid call
'
);
}
}
export
class
UserKeybindingsResolverServiceClient
implements
IUserKeybindingsResolverService
{
_serviceBrand
:
undefined
;
constructor
(
private
readonly
channel
:
IChannel
)
{
}
async
resolveUserKeybindings
(
localContent
:
string
,
remoteContent
:
string
,
baseContent
:
string
|
null
):
Promise
<
IStringDictionary
<
string
>>
{
return
this
.
channel
.
call
(
'
resolveUserKeybindings
'
,
[
localContent
,
remoteContent
,
baseContent
]);
}
}
src/vs/platform/userDataSync/common/settingsSync.ts
浏览文件 @
e5687144
...
...
@@ -135,10 +135,9 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
}
private
async
continueSync
():
Promise
<
boolean
>
{
if
(
this
.
status
!
==
SyncStatus
.
HasConflicts
)
{
return
false
;
if
(
this
.
status
=
==
SyncStatus
.
HasConflicts
)
{
await
this
.
apply
()
;
}
await
this
.
apply
();
return
true
;
}
...
...
@@ -218,8 +217,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
}
if
(
!
lastSyncData
// First time sync
||
lastSyncData
.
content
!==
localContent
// Local has
moved
forwarded
||
lastSyncData
.
content
!==
remoteContent
// Remote has
moved
forwarded
||
lastSyncData
.
content
!==
localContent
// Local has forwarded
||
lastSyncData
.
content
!==
remoteContent
// Remote has forwarded
)
{
this
.
logService
.
trace
(
'
Settings: Merging remote settings with local settings...
'
);
const
result
=
await
this
.
settingsMergeService
.
merge
(
localContent
,
remoteContent
,
lastSyncData
?
lastSyncData
.
content
:
null
,
this
.
getIgnoredSettings
());
...
...
src/vs/platform/userDataSync/common/userDataSync.ts
浏览文件 @
e5687144
...
...
@@ -15,6 +15,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat
import
{
IJSONSchema
}
from
'
vs/base/common/jsonSchema
'
;
import
{
ILogService
}
from
'
vs/platform/log/common/log
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
IStringDictionary
}
from
'
vs/base/common/collections
'
;
const
CONFIGURATION_SYNC_STORE_KEY
=
'
configurationSync.store
'
;
...
...
@@ -52,6 +53,18 @@ export function registerConfiguration(): IDisposable {
default
:
true
,
scope
:
ConfigurationScope
.
APPLICATION
,
},
'
sync.enableKeybindings
'
:
{
type
:
'
boolean
'
,
description
:
localize
(
'
sync.enableKeybindings
'
,
"
Enable synchronizing keybindings.
"
),
default
:
true
,
scope
:
ConfigurationScope
.
APPLICATION
,
},
'
sync.keybindingsPerPlatform
'
:
{
type
:
'
boolean
'
,
description
:
localize
(
'
sync.keybindingsPerPlatform
'
,
"
Synchronize keybindings per platform.
"
),
default
:
true
,
scope
:
ConfigurationScope
.
APPLICATION
,
},
'
sync.ignoredExtensions
'
:
{
'
type
'
:
'
array
'
,
description
:
localize
(
'
sync.ignoredExtensions
'
,
"
Configure extensions to be ignored while synchronizing.
"
),
...
...
@@ -132,6 +145,7 @@ export interface ISyncExtension {
export
const
enum
SyncSource
{
Settings
=
1
,
Keybindings
,
Extensions
}
...
...
@@ -173,6 +187,16 @@ export interface ISettingsMergeService {
}
export
const
IUserKeybindingsResolverService
=
createDecorator
<
IUserKeybindingsResolverService
>
(
'
IUserKeybindingsResolverService
'
);
export
interface
IUserKeybindingsResolverService
{
_serviceBrand
:
undefined
;
resolveUserKeybindings
(
localContent
:
string
,
remoteContent
:
string
,
baseContent
:
string
|
null
):
Promise
<
IStringDictionary
<
string
>>
;
}
export
const
IUserDataSyncLogService
=
createDecorator
<
IUserDataSyncLogService
>
(
'
IUserDataSyncLogService
'
);
export
interface
IUserDataSyncLogService
extends
ILogService
{
...
...
src/vs/platform/userDataSync/common/userDataSyncService.ts
浏览文件 @
e5687144
...
...
@@ -13,6 +13,7 @@ import { timeout } from 'vs/base/common/async';
import
{
ExtensionsSynchroniser
}
from
'
vs/platform/userDataSync/common/extensionsSync
'
;
import
{
IExtensionIdentifier
}
from
'
vs/platform/extensions/common/extensions
'
;
import
{
IAuthTokenService
,
AuthTokenStatus
}
from
'
vs/platform/auth/common/auth
'
;
import
{
KeybindingsSynchroniser
}
from
'
vs/platform/userDataSync/common/keybindingsSync
'
;
export
class
UserDataSyncService
extends
Disposable
implements
IUserDataSyncService
{
...
...
@@ -31,6 +32,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
get
conflictsSource
():
SyncSource
|
null
{
return
this
.
_conflictsSource
;
}
private
readonly
settingsSynchroniser
:
SettingsSynchroniser
;
private
readonly
keybindingsSynchroniser
:
KeybindingsSynchroniser
;
private
readonly
extensionsSynchroniser
:
ExtensionsSynchroniser
;
constructor
(
...
...
@@ -40,8 +42,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
)
{
super
();
this
.
settingsSynchroniser
=
this
.
_register
(
this
.
instantiationService
.
createInstance
(
SettingsSynchroniser
));
this
.
keybindingsSynchroniser
=
this
.
_register
(
this
.
instantiationService
.
createInstance
(
KeybindingsSynchroniser
));
this
.
extensionsSynchroniser
=
this
.
_register
(
this
.
instantiationService
.
createInstance
(
ExtensionsSynchroniser
));
this
.
synchronisers
=
[
this
.
settingsSynchroniser
,
this
.
extensionsSynchroniser
];
this
.
synchronisers
=
[
this
.
settingsSynchroniser
,
this
.
keybindingsSynchroniser
,
this
.
extensionsSynchroniser
];
this
.
updateStatus
();
if
(
this
.
userDataSyncStoreService
.
userDataSyncStore
)
{
...
...
@@ -111,6 +114,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if
(
source
instanceof
SettingsSynchroniser
)
{
return
SyncSource
.
Settings
;
}
if
(
source
instanceof
KeybindingsSynchroniser
)
{
return
SyncSource
.
Keybindings
;
}
}
return
null
;
}
...
...
src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts
0 → 100644
浏览文件 @
e5687144
此差异已折叠。
点击以展开。
src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
浏览文件 @
e5687144
...
...
@@ -189,6 +189,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
const
items
=
[{
id
:
'
sync.enableSettings
'
,
label
:
localize
(
'
user settings
'
,
"
User Settings
"
)
},
{
id
:
'
sync.enableKeybindings
'
,
label
:
localize
(
'
user keybindings
'
,
"
User Keybindings
"
)
},
{
id
:
'
sync.enableExtensions
'
,
label
:
localize
(
'
extensions
'
,
"
Extensions
"
)
...
...
@@ -251,13 +254,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
private
getPreviewEditorInput
():
IEditorInput
|
undefined
{
return
this
.
editorService
.
editors
.
filter
(
input
=>
isEqual
(
input
.
getResource
(),
this
.
workbenchEnvironmentService
.
settingsSyncPreviewResource
))[
0
];
return
this
.
editorService
.
editors
.
filter
(
input
=>
isEqual
(
input
.
getResource
(),
this
.
workbenchEnvironmentService
.
settingsSyncPreviewResource
)
||
isEqual
(
input
.
getResource
(),
this
.
workbenchEnvironmentService
.
keybindingsSyncPreviewResource
)
)[
0
];
}
private
async
handleConflicts
():
Promise
<
void
>
{
if
(
this
.
userDataSyncService
.
conflictsSource
===
SyncSource
.
Settings
)
{
const
conflictsResource
=
this
.
getConflictsResource
();
if
(
conflictsResource
)
{
const
resourceInput
=
{
resource
:
this
.
workbenchEnvironmentService
.
settingsSyncPreview
Resource
,
resource
:
conflicts
Resource
,
options
:
{
preserveFocus
:
false
,
pinned
:
false
,
...
...
@@ -279,6 +283,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
private
getConflictsResource
():
URI
|
null
{
if
(
this
.
userDataSyncService
.
conflictsSource
===
SyncSource
.
Settings
)
{
return
this
.
workbenchEnvironmentService
.
settingsSyncPreviewResource
;
}
if
(
this
.
userDataSyncService
.
conflictsSource
===
SyncSource
.
Keybindings
)
{
return
this
.
workbenchEnvironmentService
.
keybindingsSyncPreviewResource
;
}
return
null
;
}
private
registerActions
():
void
{
const
startSyncMenuItem
:
IMenuItem
=
{
...
...
@@ -380,6 +394,19 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
order
:
1
,
when
:
ContextKeyExpr
.
and
(
CONTEXT_SYNC_STATE
.
isEqualTo
(
SyncStatus
.
HasConflicts
),
ResourceContextKey
.
Resource
.
isEqualTo
(
this
.
workbenchEnvironmentService
.
settingsSyncPreviewResource
.
toString
())),
});
MenuRegistry
.
appendMenuItem
(
MenuId
.
EditorTitle
,
{
command
:
{
id
:
continueSyncCommandId
,
title
:
localize
(
'
continue sync
'
,
"
Sync: Continue
"
),
icon
:
{
light
:
SYNC_PUSH_LIGHT_ICON_URI
,
dark
:
SYNC_PUSH_DARK_ICON_URI
}
},
group
:
'
navigation
'
,
order
:
1
,
when
:
ContextKeyExpr
.
and
(
CONTEXT_SYNC_STATE
.
isEqualTo
(
SyncStatus
.
HasConflicts
),
ResourceContextKey
.
Resource
.
isEqualTo
(
this
.
workbenchEnvironmentService
.
keybindingsSyncPreviewResource
.
toString
())),
});
const
signOutMenuItem
:
IMenuItem
=
{
group
:
'
5_sync
'
,
...
...
src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts
浏览文件 @
e5687144
...
...
@@ -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
,
IUserKeybindingsResolverService
}
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
{
UserKeybindingsResolverServiceChannel
}
from
'
vs/platform/userDataSync/common/keybindingsSyncIpc
'
;
class
UserDataSyncServicesContribution
implements
IWorkbenchContribution
{
constructor
(
@
ISettingsMergeService
settingsMergeService
:
ISettingsMergeService
,
@
IUserKeybindingsResolverService
keybindingsMergeService
:
IUserKeybindingsResolverService
,
@
ISharedProcessService
sharedProcessService
:
ISharedProcessService
,
)
{
sharedProcessService
.
registerChannel
(
'
settingsMerge
'
,
new
SettingsMergeChannel
(
settingsMergeService
));
sharedProcessService
.
registerChannel
(
'
userKeybindingsResolver
'
,
new
UserKeybindingsResolverServiceChannel
(
keybindingsMergeService
));
}
}
...
...
src/vs/workbench/services/environment/browser/environmentService.ts
浏览文件 @
e5687144
...
...
@@ -136,6 +136,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
@
memoize
get
settingsSyncPreviewResource
():
URI
{
return
joinPath
(
this
.
userRoamingDataHome
,
'
.settings.json
'
);
}
@
memoize
get
keybindingsSyncPreviewResource
():
URI
{
return
joinPath
(
this
.
userRoamingDataHome
,
'
.keybindings.json
'
);
}
@
memoize
get
userDataSyncLogResource
():
URI
{
return
joinPath
(
this
.
options
.
logsPath
,
'
userDataSync.log
'
);
}
...
...
src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts
0 → 100644
浏览文件 @
e5687144
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
parse
}
from
'
vs/base/common/json
'
;
import
{
IUserFriendlyKeybinding
,
IKeybindingService
}
from
'
vs/platform/keybinding/common/keybinding
'
;
import
{
IUserKeybindingsResolverService
}
from
'
vs/platform/userDataSync/common/userDataSync
'
;
import
{
IStringDictionary
}
from
'
vs/base/common/collections
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
export
class
UserKeybindingsResolverService
implements
IUserKeybindingsResolverService
{
_serviceBrand
:
undefined
;
constructor
(
@
IKeybindingService
private
readonly
keybindingsService
:
IKeybindingService
)
{
}
public
async
resolveUserKeybindings
(
localContent
:
string
,
remoteContent
:
string
,
baseContent
:
string
|
null
):
Promise
<
IStringDictionary
<
string
>>
{
const
local
=
<
IUserFriendlyKeybinding
[]
>
parse
(
localContent
);
const
remote
=
<
IUserFriendlyKeybinding
[]
>
parse
(
remoteContent
);
const
base
=
baseContent
?
<
IUserFriendlyKeybinding
[]
>
parse
(
baseContent
)
:
null
;
const
keys
:
IStringDictionary
<
string
>
=
{};
for
(
const
keybinding
of
[...
local
,
...
remote
,
...(
base
||
[])])
{
keys
[
keybinding
.
key
]
=
this
.
keybindingsService
.
resolveUserBinding
(
keybinding
.
key
).
map
(
part
=>
part
.
getUserSettingsLabel
()).
join
(
'
'
);
}
return
keys
;
}
}
registerSingleton
(
IUserKeybindingsResolverService
,
UserKeybindingsResolverService
);
src/vs/workbench/services/userDataSync/common/settingsMergeService.ts
浏览文件 @
e5687144
...
...
@@ -156,7 +156,7 @@ class SettingsMergeService implements ISettingsMergeService {
const
remote
=
parse
(
remoteContent
);
const
remoteModel
=
this
.
modelService
.
createModel
(
localContent
,
this
.
modeService
.
create
(
'
jsonc
'
));
const
ignored
=
ignoredSettings
.
reduce
((
set
,
key
)
=>
{
set
.
add
(
key
);
return
set
;
},
new
Set
<
string
>
());
for
(
const
key
of
Object
.
keys
(
ignoredSettings
)
)
{
for
(
const
key
of
ignoredSettings
)
{
if
(
ignored
.
has
(
key
))
{
this
.
editSetting
(
remoteModel
,
key
,
undefined
);
this
.
editSetting
(
remoteModel
,
key
,
remote
[
key
]);
...
...
src/vs/workbench/workbench.common.main.ts
浏览文件 @
e5687144
...
...
@@ -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/userDataSync/common/keybindingsMerge
'
;
import
'
vs/workbench/services/path/common/remotePathService
'
;
import
'
vs/workbench/services/remote/common/remoteExplorerService
'
;
import
'
vs/workbench/services/workingCopy/common/workingCopyService
'
;
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录