Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
5b33ba2a
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,发现更多精彩内容 >>
提交
5b33ba2a
编写于
4月 21, 2020
作者:
S
Sandeep Somavarapu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Support workspace config based recommendations
上级
fa4c3ae9
变更
19
隐藏空白更改
内联
并排
Showing
19 changed file
with
575 addition
and
315 deletion
+575
-315
src/vs/platform/extensionManagement/common/configRemotes.ts
src/vs/platform/extensionManagement/common/configRemotes.ts
+122
-0
src/vs/platform/extensionManagement/common/extensionManagement.ts
...latform/extensionManagement/common/extensionManagement.ts
+8
-0
src/vs/platform/extensionManagement/common/extensionManagementIpc.ts
...form/extensionManagement/common/extensionManagementIpc.ts
+1
-0
src/vs/platform/extensionManagement/common/extensionTipsService.ts
...atform/extensionManagement/common/extensionTipsService.ts
+107
-0
src/vs/platform/extensionManagement/node/extensionTipsService.ts
...platform/extensionManagement/node/extensionTipsService.ts
+11
-34
src/vs/platform/extensionManagement/test/common/configRemotes.test.ts
...orm/extensionManagement/test/common/configRemotes.test.ts
+130
-0
src/vs/platform/product/common/productService.ts
src/vs/platform/product/common/productService.ts
+7
-0
src/vs/workbench/contrib/extensions/browser/configBasedRecommendations.ts
.../contrib/extensions/browser/configBasedRecommendations.ts
+132
-0
src/vs/workbench/contrib/extensions/browser/exeBasedRecommendations.ts
...nch/contrib/extensions/browser/exeBasedRecommendations.ts
+0
-7
src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts
...rib/extensions/browser/extensionRecommendationsService.ts
+11
-1
src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
...s/workbench/contrib/extensions/browser/extensionsViews.ts
+21
-10
src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts
...ch/contrib/extensions/browser/workspaceRecommendations.ts
+1
-1
src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts
.../extensions/test/electron-browser/extensionsViews.test.ts
+9
-0
src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts
.../workbench/contrib/tags/electron-browser/workspaceTags.ts
+1
-115
src/vs/workbench/contrib/tags/test/electron-browser/workspaceTags.test.ts
.../contrib/tags/test/electron-browser/workspaceTags.test.ts
+1
-114
src/vs/workbench/services/extensionManagement/common/extensionManagement.ts
...ervices/extensionManagement/common/extensionManagement.ts
+4
-1
src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts
...rvices/extensionManagement/common/extensionTipsService.ts
+0
-29
src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts
...ensionManagement/electron-browser/extensionTipsService.ts
+6
-1
src/vs/workbench/workbench.web.main.ts
src/vs/workbench/workbench.web.main.ts
+3
-2
未找到文件。
src/vs/platform/extensionManagement/common/configRemotes.ts
0 → 100644
浏览文件 @
5b33ba2a
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
endsWith
}
from
'
vs/base/common/strings
'
;
const
SshProtocolMatcher
=
/^
([^
@:
]
+@
)?([^
:
]
+
)
:/
;
const
SshUrlMatcher
=
/^
([^
@:
]
+@
)?([^
:
]
+
)
:
(
.+
)
$/
;
const
AuthorityMatcher
=
/^
([^
@
]
+@
)?([^
:
]
+
)(
:
\d
+
)?
$/
;
const
SecondLevelDomainMatcher
=
/
([^
@:.
]
+
\.[^
@:.
]
+
)(
:
\d
+
)?
$/
;
const
RemoteMatcher
=
/^
\s
*url
\s
*=
\s
*
(
.+
\S)\s
*$/mg
;
const
AnyButDot
=
/
[^
.
]
/g
;
export
const
SecondLevelDomainWhitelist
=
[
'
github.com
'
,
'
bitbucket.org
'
,
'
visualstudio.com
'
,
'
gitlab.com
'
,
'
heroku.com
'
,
'
azurewebsites.net
'
,
'
ibm.com
'
,
'
amazon.com
'
,
'
amazonaws.com
'
,
'
cloudapp.net
'
,
'
rhcloud.com
'
,
'
google.com
'
,
'
azure.com
'
];
function
stripLowLevelDomains
(
domain
:
string
):
string
|
null
{
const
match
=
domain
.
match
(
SecondLevelDomainMatcher
);
return
match
?
match
[
1
]
:
null
;
}
function
extractDomain
(
url
:
string
):
string
|
null
{
if
(
url
.
indexOf
(
'
://
'
)
===
-
1
)
{
const
match
=
url
.
match
(
SshProtocolMatcher
);
if
(
match
)
{
return
stripLowLevelDomains
(
match
[
2
]);
}
else
{
return
null
;
}
}
try
{
const
uri
=
URI
.
parse
(
url
);
if
(
uri
.
authority
)
{
return
stripLowLevelDomains
(
uri
.
authority
);
}
}
catch
(
e
)
{
// ignore invalid URIs
}
return
null
;
}
export
function
getDomainsOfRemotes
(
text
:
string
,
whitelist
:
string
[]):
string
[]
{
const
domains
=
new
Set
<
string
>
();
let
match
:
RegExpExecArray
|
null
;
while
(
match
=
RemoteMatcher
.
exec
(
text
))
{
const
domain
=
extractDomain
(
match
[
1
]);
if
(
domain
)
{
domains
.
add
(
domain
);
}
}
const
whitemap
=
whitelist
.
reduce
((
map
,
key
)
=>
{
map
[
key
]
=
true
;
return
map
;
},
Object
.
create
(
null
));
const
elements
:
string
[]
=
[];
domains
.
forEach
(
e
=>
elements
.
push
(
e
));
return
elements
.
map
(
key
=>
whitemap
[
key
]
?
key
:
key
.
replace
(
AnyButDot
,
'
a
'
));
}
function
stripPort
(
authority
:
string
):
string
|
null
{
const
match
=
authority
.
match
(
AuthorityMatcher
);
return
match
?
match
[
2
]
:
null
;
}
function
normalizeRemote
(
host
:
string
|
null
,
path
:
string
,
stripEndingDotGit
:
boolean
):
string
|
null
{
if
(
host
&&
path
)
{
if
(
stripEndingDotGit
&&
endsWith
(
path
,
'
.git
'
))
{
path
=
path
.
substr
(
0
,
path
.
length
-
4
);
}
return
(
path
.
indexOf
(
'
/
'
)
===
0
)
?
`
${
host
}${
path
}
`
:
`
${
host
}
/
${
path
}
`
;
}
return
null
;
}
function
extractRemote
(
url
:
string
,
stripEndingDotGit
:
boolean
):
string
|
null
{
if
(
url
.
indexOf
(
'
://
'
)
===
-
1
)
{
const
match
=
url
.
match
(
SshUrlMatcher
);
if
(
match
)
{
return
normalizeRemote
(
match
[
2
],
match
[
3
],
stripEndingDotGit
);
}
}
try
{
const
uri
=
URI
.
parse
(
url
);
if
(
uri
.
authority
)
{
return
normalizeRemote
(
stripPort
(
uri
.
authority
),
uri
.
path
,
stripEndingDotGit
);
}
}
catch
(
e
)
{
// ignore invalid URIs
}
return
null
;
}
export
function
getRemotes
(
text
:
string
,
stripEndingDotGit
:
boolean
=
false
):
string
[]
{
const
remotes
:
string
[]
=
[];
let
match
:
RegExpExecArray
|
null
;
while
(
match
=
RemoteMatcher
.
exec
(
text
))
{
const
remote
=
extractRemote
(
match
[
1
],
stripEndingDotGit
);
if
(
remote
)
{
remotes
.
push
(
remote
);
}
}
return
remotes
;
}
src/vs/platform/extensionManagement/common/extensionManagement.ts
浏览文件 @
5b33ba2a
...
...
@@ -222,6 +222,13 @@ export interface IGlobalExtensionEnablementService {
}
export
type
IConfigBasedExtensionTip
=
{
readonly
extensionId
:
string
,
readonly
extensionName
:
string
,
readonly
isExtensionPack
:
boolean
,
readonly
configName
:
string
,
readonly
important
:
boolean
,
};
export
type
IExecutableBasedExtensionTip
=
{
extensionId
:
string
}
&
Omit
<
Omit
<
IExeBasedExtensionTip
,
'
recommendations
'
>
,
'
important
'
>
;
export
type
IWorkspaceTips
=
{
readonly
remoteSet
:
string
[];
readonly
recommendations
:
string
[];
};
...
...
@@ -229,6 +236,7 @@ export const IExtensionTipsService = createDecorator<IExtensionTipsService>('IEx
export
interface
IExtensionTipsService
{
_serviceBrand
:
undefined
;
getConfigBasedTips
(
folder
:
URI
):
Promise
<
IConfigBasedExtensionTip
[]
>
;
getImportantExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
;
getOtherExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
;
getAllWorkspacesTips
():
Promise
<
IWorkspaceTips
[]
>
;
...
...
src/vs/platform/extensionManagement/common/extensionManagementIpc.ts
浏览文件 @
5b33ba2a
...
...
@@ -142,6 +142,7 @@ export class ExtensionTipsChannel implements IServerChannel {
call
(
context
:
any
,
command
:
string
,
args
?:
any
):
Promise
<
any
>
{
switch
(
command
)
{
case
'
getConfigBasedTips
'
:
return
this
.
service
.
getConfigBasedTips
(
URI
.
revive
(
args
[
0
]));
case
'
getImportantExecutableBasedTips
'
:
return
this
.
service
.
getImportantExecutableBasedTips
();
case
'
getOtherExecutableBasedTips
'
:
return
this
.
service
.
getOtherExecutableBasedTips
();
case
'
getAllWorkspacesTips
'
:
return
this
.
service
.
getAllWorkspacesTips
();
...
...
src/vs/platform/extensionManagement/common/extensionTipsService.ts
0 → 100644
浏览文件 @
5b33ba2a
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
IProductService
,
IConfigBasedExtensionTip
as
IRawConfigBasedExtensionTip
}
from
'
vs/platform/product/common/productService
'
;
import
{
IFileService
}
from
'
vs/platform/files/common/files
'
;
import
{
isNonEmptyArray
}
from
'
vs/base/common/arrays
'
;
import
{
IExtensionTipsService
,
IExecutableBasedExtensionTip
,
IWorkspaceTips
,
IConfigBasedExtensionTip
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
forEach
}
from
'
vs/base/common/collections
'
;
import
{
IRequestService
,
asJson
}
from
'
vs/platform/request/common/request
'
;
import
{
CancellationToken
}
from
'
vs/base/common/cancellation
'
;
import
{
ILogService
}
from
'
vs/platform/log/common/log
'
;
import
{
joinPath
}
from
'
vs/base/common/resources
'
;
import
{
getDomainsOfRemotes
}
from
'
vs/platform/extensionManagement/common/configRemotes
'
;
import
{
keys
}
from
'
vs/base/common/map
'
;
export
class
ExtensionTipsService
implements
IExtensionTipsService
{
_serviceBrand
:
any
;
private
readonly
allConfigBasedTips
:
Map
<
string
,
IRawConfigBasedExtensionTip
>
=
new
Map
<
string
,
IRawConfigBasedExtensionTip
>
();
constructor
(
@
IFileService
protected
readonly
fileService
:
IFileService
,
@
IProductService
private
readonly
productService
:
IProductService
,
@
IRequestService
private
readonly
requestService
:
IRequestService
,
@
ILogService
private
readonly
logService
:
ILogService
,
)
{
if
(
this
.
productService
.
configBasedExtensionTips
)
{
forEach
(
this
.
productService
.
configBasedExtensionTips
,
({
value
})
=>
this
.
allConfigBasedTips
.
set
(
value
.
configPath
,
value
));
}
}
getConfigBasedTips
(
folder
:
URI
):
Promise
<
IConfigBasedExtensionTip
[]
>
{
return
this
.
getValidConfigBasedTips
(
folder
);
}
getAllWorkspacesTips
():
Promise
<
IWorkspaceTips
[]
>
{
return
this
.
fetchWorkspacesTips
();
}
async
getImportantExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
{
return
[];
}
async
getOtherExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
{
return
[];
}
private
async
getValidConfigBasedTips
(
folder
:
URI
):
Promise
<
IConfigBasedExtensionTip
[]
>
{
const
result
:
IConfigBasedExtensionTip
[]
=
[];
for
(
const
[
configPath
,
tip
]
of
this
.
allConfigBasedTips
)
{
try
{
const
content
=
await
this
.
fileService
.
readFile
(
joinPath
(
folder
,
configPath
));
const
recommendationByRemote
:
Map
<
string
,
IConfigBasedExtensionTip
>
=
new
Map
<
string
,
IConfigBasedExtensionTip
>
();
forEach
(
tip
.
recommendations
,
({
key
,
value
})
=>
{
if
(
isNonEmptyArray
(
value
.
remotes
))
{
for
(
const
remote
of
value
.
remotes
)
{
recommendationByRemote
.
set
(
remote
,
{
extensionId
:
key
,
extensionName
:
value
.
name
,
configName
:
tip
.
configName
,
important
:
!!
value
.
important
,
isExtensionPack
:
!!
value
.
isExtensionPack
});
}
}
else
{
result
.
push
({
extensionId
:
key
,
extensionName
:
value
.
name
,
configName
:
tip
.
configName
,
important
:
!!
value
.
important
,
isExtensionPack
:
!!
value
.
isExtensionPack
});
}
});
const
remotes
=
getDomainsOfRemotes
(
content
.
value
.
toString
(),
keys
(
recommendationByRemote
));
remotes
.
forEach
(
remote
=>
result
.
push
(
recommendationByRemote
.
get
(
remote
)
!
));
}
catch
(
error
)
{
/* Ignore */
}
}
return
result
;
}
private
async
fetchWorkspacesTips
():
Promise
<
IWorkspaceTips
[]
>
{
if
(
!
this
.
productService
.
extensionsGallery
?.
recommendationsUrl
)
{
return
[];
}
try
{
const
context
=
await
this
.
requestService
.
request
({
type
:
'
GET
'
,
url
:
this
.
productService
.
extensionsGallery
?.
recommendationsUrl
},
CancellationToken
.
None
);
if
(
context
.
res
.
statusCode
!==
200
)
{
return
[];
}
const
result
=
await
asJson
<
{
workspaceRecommendations
?:
IWorkspaceTips
[]
}
>
(
context
);
if
(
!
result
)
{
return
[];
}
return
result
.
workspaceRecommendations
||
[];
}
catch
(
error
)
{
this
.
logService
.
error
(
error
);
return
[];
}
}
}
src/vs/platform/extensionManagement/node/extensionTipsService.ts
浏览文件 @
5b33ba2a
...
...
@@ -12,13 +12,13 @@ import { INativeEnvironmentService } from 'vs/platform/environment/node/environm
import
{
IFileService
}
from
'
vs/platform/files/common/files
'
;
import
{
isWindows
}
from
'
vs/base/common/platform
'
;
import
{
isNonEmptyArray
}
from
'
vs/base/common/arrays
'
;
import
{
IEx
tensionTipsService
,
IExecutableBasedExtensionTip
,
IWorkspaceTips
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
IEx
ecutableBasedExtensionTip
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
IStringDictionary
,
forEach
}
from
'
vs/base/common/collections
'
;
import
{
IRequestService
,
asJson
}
from
'
vs/platform/request/common/request
'
;
import
{
CancellationToken
}
from
'
vs/base/common/cancellation
'
;
import
{
IRequestService
}
from
'
vs/platform/request/common/request
'
;
import
{
ILogService
}
from
'
vs/platform/log/common/log
'
;
import
{
ExtensionTipsService
as
BaseExtensionTipsService
}
from
'
vs/platform/extensionManagement/common/extensionTipsService
'
;
export
class
ExtensionTipsService
implements
I
ExtensionTipsService
{
export
class
ExtensionTipsService
extends
Base
ExtensionTipsService
{
_serviceBrand
:
any
;
...
...
@@ -26,14 +26,15 @@ export class ExtensionTipsService implements IExtensionTipsService {
private
readonly
allOtherExecutableTips
:
IStringDictionary
<
IExeBasedExtensionTip
>
=
{};
constructor
(
@
IFileService
private
readonly
fileService
:
IFileService
,
@
IEnvironmentService
private
readonly
environmentService
:
INativeEnvironmentService
,
@
IProductService
private
readonly
productService
:
IProductService
,
@
IRequestService
private
readonly
requestService
:
IRequestService
,
@
ILogService
private
readonly
logService
:
ILogService
,
@
IFileService
fileService
:
IFileService
,
@
IProductService
productService
:
IProductService
,
@
IRequestService
requestService
:
IRequestService
,
@
ILogService
logService
:
ILogService
,
)
{
if
(
this
.
productService
.
exeBasedExtensionTips
)
{
forEach
(
this
.
productService
.
exeBasedExtensionTips
,
({
key
,
value
})
=>
{
super
(
fileService
,
productService
,
requestService
,
logService
);
if
(
productService
.
exeBasedExtensionTips
)
{
forEach
(
productService
.
exeBasedExtensionTips
,
({
key
,
value
})
=>
{
if
(
value
.
important
)
{
this
.
allImportantExecutableTips
[
key
]
=
value
;
}
else
{
...
...
@@ -43,10 +44,6 @@ export class ExtensionTipsService implements IExtensionTipsService {
}
}
getAllWorkspacesTips
():
Promise
<
IWorkspaceTips
[]
>
{
return
this
.
fetchWorkspacesTips
();
}
getImportantExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
{
return
this
.
getValidExecutableBasedExtensionTips
(
this
.
allImportantExecutableTips
);
}
...
...
@@ -99,24 +96,4 @@ export class ExtensionTipsService implements IExtensionTipsService {
return
result
;
}
private
async
fetchWorkspacesTips
():
Promise
<
IWorkspaceTips
[]
>
{
if
(
!
this
.
productService
.
extensionsGallery
?.
recommendationsUrl
)
{
return
[];
}
try
{
const
context
=
await
this
.
requestService
.
request
({
type
:
'
GET
'
,
url
:
this
.
productService
.
extensionsGallery
?.
recommendationsUrl
},
CancellationToken
.
None
);
if
(
context
.
res
.
statusCode
!==
200
)
{
return
[];
}
const
result
=
await
asJson
<
{
workspaceRecommendations
?:
IWorkspaceTips
[]
}
>
(
context
);
if
(
!
result
)
{
return
[];
}
return
result
.
workspaceRecommendations
||
[];
}
catch
(
error
)
{
this
.
logService
.
error
(
error
);
return
[];
}
}
}
src/vs/platform/extensionManagement/test/common/configRemotes.test.ts
0 → 100644
浏览文件 @
5b33ba2a
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
*
as
assert
from
'
assert
'
;
import
{
getDomainsOfRemotes
,
getRemotes
}
from
'
vs/platform/extensionManagement/common/configRemotes
'
;
suite
(
'
Config Remotes
'
,
()
=>
{
const
whitelist
=
[
'
github.com
'
,
'
github2.com
'
,
'
github3.com
'
,
'
example.com
'
,
'
example2.com
'
,
'
example3.com
'
,
'
server.org
'
,
'
server2.org
'
,
];
test
(
'
HTTPS remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
),
whitelist
),
[
'
github.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
),
whitelist
),
[
'
example.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
),
whitelist
),
[
'
github2.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
),
whitelist
),
[
'
github3.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
),
whitelist
),
[
'
example2.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
),
whitelist
),
[
'
example3.com
'
]);
});
test
(
'
SSH remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
),
whitelist
),
[
'
server.org
'
]);
});
test
(
'
SCP-like remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
),
whitelist
),
[
'
github.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
user@git.server.org:project.git
'
),
whitelist
),
[
'
server.org
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
git.server2.org:project.git
'
),
whitelist
),
[
'
server2.org
'
]);
});
test
(
'
Local remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
/opt/git/project.git
'
),
whitelist
),
[]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
file:///opt/git/project.git
'
),
whitelist
),
[]);
});
test
(
'
Multiple remotes
'
,
function
()
{
const
config
=
[
'
https://github.com/Microsoft/vscode.git
'
,
'
https://git.example.com/gitproject.git
'
].
map
(
remote
).
join
(
''
);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
config
,
whitelist
).
sort
(),
[
'
example.com
'
,
'
github.com
'
]);
});
test
(
'
Whitelisting
'
,
()
=>
{
const
config
=
[
'
https://github.com/Microsoft/vscode.git
'
,
'
https://git.foobar.com/gitproject.git
'
].
map
(
remote
).
join
(
''
);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
config
,
whitelist
).
sort
(),
[
'
aaaaaa.aaa
'
,
'
github.com
'
]);
});
test
(
'
HTTPS remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
)),
[
'
github.com/Microsoft/vscode.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
)),
[
'
git.example.com/gitproject.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
)),
[
'
github2.com/username/repository.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
)),
[
'
github3.com/username/repository.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
)),
[
'
example2.com/username/repository.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
)),
[
'
example3.com/username/repository.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
),
true
),
[
'
github.com/Microsoft/vscode
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
),
true
),
[
'
git.example.com/gitproject
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
),
true
),
[
'
github2.com/username/repository
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
),
true
),
[
'
github3.com/username/repository
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
),
true
),
[
'
example2.com/username/repository
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
),
true
),
[
'
example3.com/username/repository
'
]);
// Compare Striped .git with no .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
),
true
),
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
),
true
),
getRemotes
(
remote
(
'
https://git.example.com/gitproject
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://username@github2.com/username/repository
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository
'
)));
});
test
(
'
SSH remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
)),
[
'
git.server.org/project.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
),
true
),
[
'
git.server.org/project
'
]);
// Compare Striped .git with no .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
),
true
),
getRemotes
(
remote
(
'
ssh://user@git.server.org/project
'
)));
});
test
(
'
SCP-like remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
)),
[
'
github.com/Microsoft/vscode.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
user@git.server.org:project.git
'
)),
[
'
git.server.org/project.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git.server2.org:project.git
'
)),
[
'
git.server2.org/project.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
),
true
),
[
'
github.com/Microsoft/vscode
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
user@git.server.org:project.git
'
),
true
),
[
'
git.server.org/project
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git.server2.org:project.git
'
),
true
),
[
'
git.server2.org/project
'
]);
// Compare Striped .git with no .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
),
true
),
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
user@git.server.org:project.git
'
),
true
),
getRemotes
(
remote
(
'
user@git.server.org:project
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git.server2.org:project.git
'
),
true
),
getRemotes
(
remote
(
'
git.server2.org:project
'
)));
});
test
(
'
Local remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
/opt/git/project.git
'
)),
[]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
file:///opt/git/project.git
'
)),
[]);
});
test
(
'
Multiple remotes to be hashed
'
,
function
()
{
const
config
=
[
'
https://github.com/Microsoft/vscode.git
'
,
'
https://git.example.com/gitproject.git
'
].
map
(
remote
).
join
(
'
'
);
assert
.
deepStrictEqual
(
getRemotes
(
config
),
[
'
github.com/Microsoft/vscode.git
'
,
'
git.example.com/gitproject.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
config
,
true
),
[
'
github.com/Microsoft/vscode
'
,
'
git.example.com/gitproject
'
]);
// Compare Striped .git with no .git
const
noDotGitConfig
=
[
'
https://github.com/Microsoft/vscode
'
,
'
https://git.example.com/gitproject
'
].
map
(
remote
).
join
(
'
'
);
assert
.
deepStrictEqual
(
getRemotes
(
config
,
true
),
getRemotes
(
noDotGitConfig
));
});
function
remote
(
url
:
string
):
string
{
return
`[remote "origin"]
url =
${
url
}
fetch = +refs/heads/*:refs/remotes/origin/*
`
;
}
});
src/vs/platform/product/common/productService.ts
浏览文件 @
5b33ba2a
...
...
@@ -60,6 +60,7 @@ export interface IProductConfiguration {
readonly
extensionTips
?:
{
[
id
:
string
]:
string
;
};
readonly
extensionImportantTips
?:
{
[
id
:
string
]:
{
name
:
string
;
pattern
:
string
;
isExtensionPack
?:
boolean
};
};
readonly
configBasedExtensionTips
?:
{
[
id
:
string
]:
IConfigBasedExtensionTip
;
};
readonly
exeBasedExtensionTips
?:
{
[
id
:
string
]:
IExeBasedExtensionTip
;
};
readonly
remoteExtensionTips
?:
{
[
remoteName
:
string
]:
IRemoteExtensionTip
;
};
readonly
extensionKeywords
?:
{
[
extension
:
string
]:
readonly
string
[];
};
...
...
@@ -119,6 +120,12 @@ export interface IProductConfiguration {
readonly
'
configurationSync.store
'
?:
ConfigurationSyncStore
;
}
export
interface
IConfigBasedExtensionTip
{
configPath
:
string
;
configName
:
string
;
recommendations
:
IStringDictionary
<
{
name
:
string
,
remotes
?:
string
[],
important
?:
boolean
,
isExtensionPack
?:
boolean
}
>
;
}
export
interface
IExeBasedExtensionTip
{
friendlyName
:
string
;
windowsPath
?:
string
;
...
...
src/vs/workbench/contrib/extensions/browser/configBasedRecommendations.ts
0 → 100644
浏览文件 @
5b33ba2a
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
IExtensionTipsService
,
IExtensionManagementService
,
ILocalExtension
,
IConfigBasedExtensionTip
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
ITelemetryService
}
from
'
vs/platform/telemetry/common/telemetry
'
;
import
{
ExtensionRecommendations
,
ExtensionRecommendation
}
from
'
vs/workbench/contrib/extensions/browser/extensionRecommendations
'
;
import
{
localize
}
from
'
vs/nls
'
;
import
{
ExtensionType
}
from
'
vs/platform/extensions/common/extensions
'
;
import
{
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
INotificationService
}
from
'
vs/platform/notification/common/notification
'
;
import
{
ExtensionRecommendationReason
}
from
'
vs/workbench/services/extensionManagement/common/extensionManagement
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
IStorageService
}
from
'
vs/platform/storage/common/storage
'
;
import
{
IStorageKeysSyncRegistryService
}
from
'
vs/platform/userDataSync/common/storageKeys
'
;
import
{
IWorkspaceContextService
,
IWorkspaceFoldersChangeEvent
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
distinct
}
from
'
vs/base/common/arrays
'
;
import
{
values
}
from
'
vs/base/common/map
'
;
export
class
ConfigBasedRecommendations
extends
ExtensionRecommendations
{
private
importantTips
:
IConfigBasedExtensionTip
[]
=
[];
private
otherTips
:
IConfigBasedExtensionTip
[]
=
[];
private
_recommendations
:
ExtensionRecommendation
[]
=
[];
get
recommendations
():
ReadonlyArray
<
ExtensionRecommendation
>
{
return
this
.
_recommendations
;
}
constructor
(
isExtensionAllowedToBeRecommended
:
(
extensionId
:
string
)
=>
boolean
,
@
IExtensionTipsService
private
readonly
extensionTipsService
:
IExtensionTipsService
,
@
IExtensionManagementService
private
readonly
extensionManagementService
:
IExtensionManagementService
,
@
IWorkspaceContextService
private
readonly
workspaceContextService
:
IWorkspaceContextService
,
@
IInstantiationService
instantiationService
:
IInstantiationService
,
@
IConfigurationService
configurationService
:
IConfigurationService
,
@
INotificationService
notificationService
:
INotificationService
,
@
ITelemetryService
telemetryService
:
ITelemetryService
,
@
IStorageService
storageService
:
IStorageService
,
@
IStorageKeysSyncRegistryService
storageKeysSyncRegistryService
:
IStorageKeysSyncRegistryService
,
)
{
super
(
isExtensionAllowedToBeRecommended
,
instantiationService
,
configurationService
,
notificationService
,
telemetryService
,
storageService
,
storageKeysSyncRegistryService
);
}
protected
async
doActivate
():
Promise
<
void
>
{
await
this
.
fetch
();
this
.
_register
(
this
.
workspaceContextService
.
onDidChangeWorkspaceFolders
(
e
=>
this
.
onWorkspaceFoldersChanged
(
e
)));
this
.
promptWorkspaceRecommendations
();
}
private
async
fetch
():
Promise
<
void
>
{
const
workspace
=
this
.
workspaceContextService
.
getWorkspace
();
const
importantTips
:
Map
<
string
,
IConfigBasedExtensionTip
>
=
new
Map
<
string
,
IConfigBasedExtensionTip
>
();
const
otherTips
:
Map
<
string
,
IConfigBasedExtensionTip
>
=
new
Map
<
string
,
IConfigBasedExtensionTip
>
();
for
(
const
folder
of
workspace
.
folders
)
{
const
configBasedTips
=
await
this
.
extensionTipsService
.
getConfigBasedTips
(
folder
.
uri
);
for
(
const
tip
of
configBasedTips
)
{
if
(
tip
.
important
)
{
importantTips
.
set
(
tip
.
extensionId
,
tip
);
}
else
{
otherTips
.
set
(
tip
.
extensionId
,
tip
);
}
}
}
this
.
importantTips
=
values
(
importantTips
);
this
.
otherTips
=
values
(
otherTips
).
filter
(
tip
=>
!
importantTips
.
has
(
tip
.
extensionId
));
this
.
_recommendations
=
[...
this
.
importantTips
,
...
this
.
otherTips
].
map
(
tip
=>
this
.
toExtensionRecommendation
(
tip
));
}
private
async
promptWorkspaceRecommendations
():
Promise
<
void
>
{
if
(
this
.
hasToIgnoreRecommendationNotifications
())
{
return
;
}
if
(
this
.
importantTips
.
length
===
0
)
{
return
;
}
const
local
=
await
this
.
extensionManagementService
.
getInstalled
(
ExtensionType
.
User
);
const
{
uninstalled
}
=
this
.
groupByInstalled
(
distinct
(
this
.
importantTips
.
map
(({
extensionId
})
=>
extensionId
)),
local
);
if
(
uninstalled
.
length
===
0
)
{
return
;
}
const
importantExtensions
=
this
.
filterIgnoredOrNotAllowed
(
uninstalled
);
if
(
importantExtensions
.
length
===
0
)
{
return
;
}
for
(
const
extension
of
importantExtensions
)
{
const
tip
=
this
.
importantTips
.
filter
(
tip
=>
tip
.
extensionId
===
extension
)[
0
];
const
message
=
tip
.
isExtensionPack
?
localize
(
'
extensionPackRecommended
'
,
"
The '{0}' extension pack is recommended for this workspace.
"
,
tip
.
extensionName
)
:
localize
(
'
extensionRecommended
'
,
"
The '{0}' extension is recommended for this workspace.
"
,
tip
.
extensionName
);
this
.
promptImportantExtensionInstallNotification
(
extension
,
message
);
}
}
private
groupByInstalled
(
recommendationsToSuggest
:
string
[],
local
:
ILocalExtension
[]):
{
installed
:
string
[],
uninstalled
:
string
[]
}
{
const
installed
:
string
[]
=
[],
uninstalled
:
string
[]
=
[];
const
installedExtensionsIds
=
local
.
reduce
((
result
,
i
)
=>
{
result
.
add
(
i
.
identifier
.
id
.
toLowerCase
());
return
result
;
},
new
Set
<
string
>
());
recommendationsToSuggest
.
forEach
(
id
=>
{
if
(
installedExtensionsIds
.
has
(
id
.
toLowerCase
()))
{
installed
.
push
(
id
);
}
else
{
uninstalled
.
push
(
id
);
}
});
return
{
installed
,
uninstalled
};
}
private
async
onWorkspaceFoldersChanged
(
event
:
IWorkspaceFoldersChangeEvent
):
Promise
<
void
>
{
if
(
event
.
added
.
length
)
{
const
oldImportantRecommended
=
this
.
importantTips
;
await
this
.
fetch
();
// Suggest only if at least one of the newly added recommendations was not suggested before
if
(
this
.
importantTips
.
some
(
current
=>
oldImportantRecommended
.
every
(
old
=>
current
.
extensionId
!==
old
.
extensionId
)))
{
return
this
.
promptWorkspaceRecommendations
();
}
}
}
private
toExtensionRecommendation
(
tip
:
IConfigBasedExtensionTip
):
ExtensionRecommendation
{
return
{
extensionId
:
tip
.
extensionId
,
source
:
'
config
'
,
reason
:
{
reasonId
:
ExtensionRecommendationReason
.
WorkspaceConfig
,
reasonText
:
localize
(
'
exeBasedRecommendation
'
,
"
This extension is recommended because the workspace is configured with.
"
,
tip
.
configName
)
}
};
}
}
src/vs/workbench/contrib/extensions/browser/exeBasedRecommendations.ts
浏览文件 @
5b33ba2a
...
...
@@ -15,8 +15,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import
{
basename
}
from
'
vs/base/common/path
'
;
import
{
ExtensionRecommendationReason
}
from
'
vs/workbench/services/extensionManagement/common/extensionManagement
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
ShowRecommendationsOnlyOnDemandKey
}
from
'
vs/workbench/contrib/extensions/common/extensions
'
;
import
{
ILifecycleService
,
LifecyclePhase
}
from
'
vs/platform/lifecycle/common/lifecycle
'
;
import
{
IStorageService
}
from
'
vs/platform/storage/common/storage
'
;
import
{
IStorageKeysSyncRegistryService
}
from
'
vs/platform/userDataSync/common/storageKeys
'
;
...
...
@@ -34,7 +32,6 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
isExtensionAllowedToBeRecommended
:
(
extensionId
:
string
)
=>
boolean
,
@
IExtensionTipsService
private
readonly
extensionTipsService
:
IExtensionTipsService
,
@
IExtensionManagementService
private
readonly
extensionManagementService
:
IExtensionManagementService
,
@
ILifecycleService
lifecycleService
:
ILifecycleService
,
@
IInstantiationService
instantiationService
:
IInstantiationService
,
@
IConfigurationService
configurationService
:
IConfigurationService
,
@
INotificationService
notificationService
:
INotificationService
,
...
...
@@ -49,10 +46,6 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
Also fetch important exe based recommendations for reporting telemetry
*/
timeout
(
3000
).
then
(()
=>
this
.
fetchAndPromptImportantExeBasedRecommendations
());
if
(
!
this
.
configurationService
.
getValue
<
boolean
>
(
ShowRecommendationsOnlyOnDemandKey
))
{
lifecycleService
.
when
(
LifecyclePhase
.
Eventually
).
then
(()
=>
this
.
activate
());
}
}
protected
async
doActivate
():
Promise
<
void
>
{
...
...
src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts
浏览文件 @
5b33ba2a
...
...
@@ -24,6 +24,7 @@ import { FileBasedRecommendations } from 'vs/workbench/contrib/extensions/browse
import
{
KeymapRecommendations
}
from
'
vs/workbench/contrib/extensions/browser/keymapRecommendations
'
;
import
{
ExtensionRecommendation
}
from
'
vs/workbench/contrib/extensions/browser/extensionRecommendations
'
;
import
{
IStorageKeysSyncRegistryService
}
from
'
vs/platform/userDataSync/common/storageKeys
'
;
import
{
ConfigBasedRecommendations
}
from
'
vs/workbench/contrib/extensions/browser/configBasedRecommendations
'
;
type
IgnoreRecommendationClassification
=
{
recommendationReason
:
{
classification
:
'
SystemMetaData
'
,
purpose
:
'
FeatureInsight
'
,
isMeasurement
:
true
};
...
...
@@ -40,6 +41,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
private
readonly
fileBasedRecommendations
:
FileBasedRecommendations
;
private
readonly
workspaceRecommendations
:
WorkspaceRecommendations
;
private
readonly
experimentalRecommendations
:
ExperimentalRecommendations
;
private
readonly
configBasedRecommendations
:
ConfigBasedRecommendations
;
private
readonly
exeBasedRecommendations
:
ExeBasedRecommendations
;
private
readonly
dynamicWorkspaceRecommendations
:
DynamicWorkspaceRecommendations
;
private
readonly
keymapRecommendations
:
KeymapRecommendations
;
...
...
@@ -72,6 +74,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
this
.
workspaceRecommendations
=
instantiationService
.
createInstance
(
WorkspaceRecommendations
,
isExtensionAllowedToBeRecommended
);
this
.
fileBasedRecommendations
=
instantiationService
.
createInstance
(
FileBasedRecommendations
,
isExtensionAllowedToBeRecommended
);
this
.
experimentalRecommendations
=
instantiationService
.
createInstance
(
ExperimentalRecommendations
,
isExtensionAllowedToBeRecommended
);
this
.
configBasedRecommendations
=
instantiationService
.
createInstance
(
ConfigBasedRecommendations
,
isExtensionAllowedToBeRecommended
);
this
.
exeBasedRecommendations
=
instantiationService
.
createInstance
(
ExeBasedRecommendations
,
isExtensionAllowedToBeRecommended
);
this
.
dynamicWorkspaceRecommendations
=
instantiationService
.
createInstance
(
DynamicWorkspaceRecommendations
,
isExtensionAllowedToBeRecommended
);
this
.
keymapRecommendations
=
instantiationService
.
createInstance
(
KeymapRecommendations
,
isExtensionAllowedToBeRecommended
);
...
...
@@ -102,7 +105,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
}
private
async
activateProactiveRecommendations
():
Promise
<
void
>
{
await
Promise
.
all
([
this
.
dynamicWorkspaceRecommendations
.
activate
(),
this
.
exeBasedRecommendations
.
activate
()]);
await
Promise
.
all
([
this
.
dynamicWorkspaceRecommendations
.
activate
(),
this
.
exeBasedRecommendations
.
activate
()
,
this
.
configBasedRecommendations
.
activate
()
]);
}
getAllRecommendationsWithReason
():
{
[
id
:
string
]:
{
reasonId
:
ExtensionRecommendationReason
,
reasonText
:
string
};
}
{
...
...
@@ -113,6 +116,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
const
allRecommendations
=
[
...
this
.
dynamicWorkspaceRecommendations
.
recommendations
,
...
this
.
configBasedRecommendations
.
recommendations
,
...
this
.
exeBasedRecommendations
.
recommendations
,
...
this
.
experimentalRecommendations
.
recommendations
,
...
this
.
fileBasedRecommendations
.
recommendations
,
...
...
@@ -129,10 +133,16 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
return
output
;
}
async
getConfigBasedRecommendations
():
Promise
<
IExtensionRecommendation
[]
>
{
await
this
.
configBasedRecommendations
.
activate
();
return
this
.
toExtensionRecommendations
(
this
.
configBasedRecommendations
.
recommendations
);
}
async
getOtherRecommendations
():
Promise
<
IExtensionRecommendation
[]
>
{
await
this
.
activateProactiveRecommendations
();
const
recommendations
=
[
...
this
.
configBasedRecommendations
.
recommendations
,
...
this
.
exeBasedRecommendations
.
recommendations
,
...
this
.
dynamicWorkspaceRecommendations
.
recommendations
,
...
this
.
experimentalRecommendations
.
recommendations
...
...
src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
浏览文件 @
5b33ba2a
...
...
@@ -553,12 +553,13 @@ export class ExtensionsListView extends ViewPane {
.
then
(
result
=>
result
.
filter
(
e
=>
e
.
type
===
ExtensionType
.
User
))
.
then
(
local
=>
{
const
fileBasedRecommendations
=
this
.
tipsService
.
getFileBasedRecommendations
();
const
configBasedRecommendationsPromise
=
this
.
tipsService
.
getConfigBasedRecommendations
();
const
othersPromise
=
this
.
tipsService
.
getOtherRecommendations
();
const
workspacePromise
=
this
.
tipsService
.
getWorkspaceRecommendations
();
return
Promise
.
all
([
othersPromise
,
workspacePromise
])
.
then
(([
others
,
workspaceRecommendations
])
=>
{
const
names
=
this
.
getTrimmedRecommendations
(
local
,
value
,
fileBasedRecommendations
,
others
,
workspaceRecommendations
);
return
Promise
.
all
([
othersPromise
,
workspacePromise
,
configBasedRecommendationsPromise
])
.
then
(([
others
,
workspaceRecommendations
,
configBasedRecommendations
])
=>
{
const
names
=
this
.
getTrimmedRecommendations
(
local
,
value
,
fileBasedRecommendations
,
configBasedRecommendations
,
others
,
workspaceRecommendations
);
const
recommendationsWithReason
=
this
.
tipsService
.
getAllRecommendationsWithReason
();
/* __GDPR__
"extensionAllRecommendations:open" : {
...
...
@@ -608,15 +609,17 @@ export class ExtensionsListView extends ViewPane {
.
then
(
result
=>
result
.
filter
(
e
=>
e
.
type
===
ExtensionType
.
User
))
.
then
(
local
=>
{
let
fileBasedRecommendations
=
this
.
tipsService
.
getFileBasedRecommendations
();
const
configBasedRecommendationsPromise
=
this
.
tipsService
.
getConfigBasedRecommendations
();
const
othersPromise
=
this
.
tipsService
.
getOtherRecommendations
();
const
workspacePromise
=
this
.
tipsService
.
getWorkspaceRecommendations
();
return
Promise
.
all
([
othersPromise
,
workspacePromise
])
.
then
(([
others
,
workspaceRecommendations
])
=>
{
return
Promise
.
all
([
othersPromise
,
workspacePromise
,
configBasedRecommendationsPromise
])
.
then
(([
others
,
workspaceRecommendations
,
configBasedRecommendations
])
=>
{
configBasedRecommendations
=
configBasedRecommendations
.
filter
(
x
=>
workspaceRecommendations
.
every
(({
extensionId
})
=>
x
.
extensionId
!==
extensionId
));
fileBasedRecommendations
=
fileBasedRecommendations
.
filter
(
x
=>
workspaceRecommendations
.
every
(({
extensionId
})
=>
x
.
extensionId
!==
extensionId
));
others
=
others
.
filter
(
x
=>
workspaceRecommendations
.
every
(({
extensionId
})
=>
x
.
extensionId
!==
extensionId
));
const
names
=
this
.
getTrimmedRecommendations
(
local
,
value
,
fileBasedRecommendations
,
others
,
[]);
const
names
=
this
.
getTrimmedRecommendations
(
local
,
value
,
fileBasedRecommendations
,
configBasedRecommendations
,
others
,
[]);
const
recommendationsWithReason
=
this
.
tipsService
.
getAllRecommendationsWithReason
();
/* __GDPR__
...
...
@@ -649,28 +652,36 @@ export class ExtensionsListView extends ViewPane {
}
// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
private
getTrimmedRecommendations
(
installedExtensions
:
IExtension
[],
value
:
string
,
fileBasedRecommendations
:
IExtensionRecommendation
[],
otherRecommendations
:
IExtensionRecommendation
[],
workspaceRecommendations
:
IExtensionRecommendation
[]):
string
[]
{
const
totalCount
=
8
;
private
getTrimmedRecommendations
(
installedExtensions
:
IExtension
[],
value
:
string
,
fileBasedRecommendations
:
IExtensionRecommendation
[],
configBasedRecommendations
:
IExtensionRecommendation
[],
otherRecommendations
:
IExtensionRecommendation
[],
workspaceRecommendations
:
IExtensionRecommendation
[]):
string
[]
{
const
totalCount
=
10
;
workspaceRecommendations
=
workspaceRecommendations
.
filter
(
recommendation
=>
{
return
!
this
.
isRecommendationInstalled
(
recommendation
,
installedExtensions
)
&&
recommendation
.
extensionId
.
toLowerCase
().
indexOf
(
value
)
>
-
1
;
});
configBasedRecommendations
=
configBasedRecommendations
.
filter
(
recommendation
=>
{
return
!
this
.
isRecommendationInstalled
(
recommendation
,
installedExtensions
)
&&
workspaceRecommendations
.
every
(
workspaceRecommendation
=>
workspaceRecommendation
.
extensionId
!==
recommendation
.
extensionId
)
&&
recommendation
.
extensionId
.
toLowerCase
().
indexOf
(
value
)
>
-
1
;
});
fileBasedRecommendations
=
fileBasedRecommendations
.
filter
(
recommendation
=>
{
return
!
this
.
isRecommendationInstalled
(
recommendation
,
installedExtensions
)
&&
workspaceRecommendations
.
every
(
workspaceRecommendation
=>
workspaceRecommendation
.
extensionId
!==
recommendation
.
extensionId
)
&&
configBasedRecommendations
.
every
(
configBasedRecommendation
=>
configBasedRecommendation
.
extensionId
!==
recommendation
.
extensionId
)
&&
recommendation
.
extensionId
.
toLowerCase
().
indexOf
(
value
)
>
-
1
;
});
otherRecommendations
=
otherRecommendations
.
filter
(
recommendation
=>
{
return
!
this
.
isRecommendationInstalled
(
recommendation
,
installedExtensions
)
&&
fileBasedRecommendations
.
every
(
fileBasedRecommendation
=>
fileBasedRecommendation
.
extensionId
!==
recommendation
.
extensionId
)
&&
workspaceRecommendations
.
every
(
workspaceRecommendation
=>
workspaceRecommendation
.
extensionId
!==
recommendation
.
extensionId
)
&&
configBasedRecommendations
.
every
(
configBasedRecommendation
=>
configBasedRecommendation
.
extensionId
!==
recommendation
.
extensionId
)
&&
recommendation
.
extensionId
.
toLowerCase
().
indexOf
(
value
)
>
-
1
;
});
const
otherCount
=
Math
.
min
(
2
,
otherRecommendations
.
length
);
const
fileBasedCount
=
Math
.
min
(
fileBasedRecommendations
.
length
,
totalCount
-
workspaceRecommendations
.
length
-
otherCount
);
const
recommendations
=
workspaceRecommendations
;
const
fileBasedCount
=
Math
.
min
(
fileBasedRecommendations
.
length
,
totalCount
-
workspaceRecommendations
.
length
-
configBasedRecommendations
.
length
-
otherCount
);
const
recommendations
=
[...
workspaceRecommendations
,
...
configBasedRecommendations
]
;
recommendations
.
push
(...
fileBasedRecommendations
.
splice
(
0
,
fileBasedCount
));
recommendations
.
push
(...
otherRecommendations
.
splice
(
0
,
otherCount
));
...
...
src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts
浏览文件 @
5b33ba2a
...
...
@@ -233,7 +233,7 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
private
async
onWorkspaceFoldersChanged
(
event
:
IWorkspaceFoldersChangeEvent
):
Promise
<
void
>
{
if
(
event
.
added
.
length
)
{
const
oldWorkspaceRecommended
=
this
.
_recommendations
;
await
this
.
activate
();
await
this
.
fetch
();
// Suggest only if at least one of the newly added recommendations was not suggested before
if
(
this
.
_recommendations
.
some
(
current
=>
oldWorkspaceRecommended
.
every
(
old
=>
current
.
extensionId
!==
old
.
extensionId
)))
{
this
.
promptWorkspaceRecommendations
();
...
...
src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts
浏览文件 @
5b33ba2a
...
...
@@ -67,6 +67,7 @@ suite('ExtensionsListView Tests', () => {
const
workspaceRecommendationA
=
aGalleryExtension
(
'
workspace-recommendation-A
'
);
const
workspaceRecommendationB
=
aGalleryExtension
(
'
workspace-recommendation-B
'
);
const
configBasedRecommendationA
=
aGalleryExtension
(
'
configbased-recommendation-A
'
);
const
fileBasedRecommendationA
=
aGalleryExtension
(
'
filebased-recommendation-A
'
);
const
fileBasedRecommendationB
=
aGalleryExtension
(
'
filebased-recommendation-B
'
);
const
otherRecommendationA
=
aGalleryExtension
(
'
other-recommendation-A
'
);
...
...
@@ -114,12 +115,18 @@ suite('ExtensionsListView Tests', () => {
reasons
[
fileBasedRecommendationA
.
identifier
.
id
]
=
{
reasonId
:
ExtensionRecommendationReason
.
File
};
reasons
[
fileBasedRecommendationB
.
identifier
.
id
]
=
{
reasonId
:
ExtensionRecommendationReason
.
File
};
reasons
[
otherRecommendationA
.
identifier
.
id
]
=
{
reasonId
:
ExtensionRecommendationReason
.
Executable
};
reasons
[
configBasedRecommendationA
.
identifier
.
id
]
=
{
reasonId
:
ExtensionRecommendationReason
.
WorkspaceConfig
};
instantiationService
.
stub
(
IExtensionRecommendationsService
,
<
Partial
<
IExtensionRecommendationsService
>>
{
getWorkspaceRecommendations
()
{
return
Promise
.
resolve
([
{
extensionId
:
workspaceRecommendationA
.
identifier
.
id
},
{
extensionId
:
workspaceRecommendationB
.
identifier
.
id
}]);
},
getConfigBasedRecommendations
()
{
return
Promise
.
resolve
([
{
extensionId
:
configBasedRecommendationA
.
identifier
.
id
}
]);
},
getFileBasedRecommendations
()
{
return
[
{
extensionId
:
fileBasedRecommendationA
.
identifier
.
id
},
...
...
@@ -341,6 +348,7 @@ suite('ExtensionsListView Tests', () => {
test
(
'
Test @recommended query
'
,
()
=>
{
const
allRecommendedExtensions
=
[
configBasedRecommendationA
,
fileBasedRecommendationA
,
fileBasedRecommendationB
,
otherRecommendationA
...
...
@@ -365,6 +373,7 @@ suite('ExtensionsListView Tests', () => {
const
allRecommendedExtensions
=
[
workspaceRecommendationA
,
workspaceRecommendationB
,
configBasedRecommendationA
,
fileBasedRecommendationA
,
fileBasedRecommendationB
,
otherRecommendationA
...
...
src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts
浏览文件 @
5b33ba2a
...
...
@@ -10,127 +10,13 @@ import { IFileService, IFileStat } from 'vs/platform/files/common/files';
import
{
ITelemetryService
}
from
'
vs/platform/telemetry/common/telemetry
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
IWorkbenchContribution
}
from
'
vs/workbench/common/contributions
'
;
import
{
endsWith
}
from
'
vs/base/common/strings
'
;
import
{
ITextFileService
,
}
from
'
vs/workbench/services/textfile/common/textfiles
'
;
import
{
ISharedProcessService
}
from
'
vs/platform/ipc/electron-browser/sharedProcessService
'
;
import
{
IWorkspaceTagsService
,
Tags
}
from
'
vs/workbench/contrib/tags/common/workspaceTags
'
;
import
{
IWorkspaceInformation
}
from
'
vs/platform/diagnostics/common/diagnostics
'
;
import
{
IRequestService
}
from
'
vs/platform/request/common/request
'
;
import
{
isWindows
}
from
'
vs/base/common/platform
'
;
const
SshProtocolMatcher
=
/^
([^
@:
]
+@
)?([^
:
]
+
)
:/
;
const
SshUrlMatcher
=
/^
([^
@:
]
+@
)?([^
:
]
+
)
:
(
.+
)
$/
;
const
AuthorityMatcher
=
/^
([^
@
]
+@
)?([^
:
]
+
)(
:
\d
+
)?
$/
;
const
SecondLevelDomainMatcher
=
/
([^
@:.
]
+
\.[^
@:.
]
+
)(
:
\d
+
)?
$/
;
const
RemoteMatcher
=
/^
\s
*url
\s
*=
\s
*
(
.+
\S)\s
*$/mg
;
const
AnyButDot
=
/
[^
.
]
/g
;
const
SecondLevelDomainWhitelist
=
[
'
github.com
'
,
'
bitbucket.org
'
,
'
visualstudio.com
'
,
'
gitlab.com
'
,
'
heroku.com
'
,
'
azurewebsites.net
'
,
'
ibm.com
'
,
'
amazon.com
'
,
'
amazonaws.com
'
,
'
cloudapp.net
'
,
'
rhcloud.com
'
,
'
google.com
'
,
'
azure.com
'
];
function
stripLowLevelDomains
(
domain
:
string
):
string
|
null
{
const
match
=
domain
.
match
(
SecondLevelDomainMatcher
);
return
match
?
match
[
1
]
:
null
;
}
function
extractDomain
(
url
:
string
):
string
|
null
{
if
(
url
.
indexOf
(
'
://
'
)
===
-
1
)
{
const
match
=
url
.
match
(
SshProtocolMatcher
);
if
(
match
)
{
return
stripLowLevelDomains
(
match
[
2
]);
}
else
{
return
null
;
}
}
try
{
const
uri
=
URI
.
parse
(
url
);
if
(
uri
.
authority
)
{
return
stripLowLevelDomains
(
uri
.
authority
);
}
}
catch
(
e
)
{
// ignore invalid URIs
}
return
null
;
}
export
function
getDomainsOfRemotes
(
text
:
string
,
whitelist
:
string
[]):
string
[]
{
const
domains
=
new
Set
<
string
>
();
let
match
:
RegExpExecArray
|
null
;
while
(
match
=
RemoteMatcher
.
exec
(
text
))
{
const
domain
=
extractDomain
(
match
[
1
]);
if
(
domain
)
{
domains
.
add
(
domain
);
}
}
const
whitemap
=
whitelist
.
reduce
((
map
,
key
)
=>
{
map
[
key
]
=
true
;
return
map
;
},
Object
.
create
(
null
));
const
elements
:
string
[]
=
[];
domains
.
forEach
(
e
=>
elements
.
push
(
e
));
return
elements
.
map
(
key
=>
whitemap
[
key
]
?
key
:
key
.
replace
(
AnyButDot
,
'
a
'
));
}
function
stripPort
(
authority
:
string
):
string
|
null
{
const
match
=
authority
.
match
(
AuthorityMatcher
);
return
match
?
match
[
2
]
:
null
;
}
function
normalizeRemote
(
host
:
string
|
null
,
path
:
string
,
stripEndingDotGit
:
boolean
):
string
|
null
{
if
(
host
&&
path
)
{
if
(
stripEndingDotGit
&&
endsWith
(
path
,
'
.git
'
))
{
path
=
path
.
substr
(
0
,
path
.
length
-
4
);
}
return
(
path
.
indexOf
(
'
/
'
)
===
0
)
?
`
${
host
}${
path
}
`
:
`
${
host
}
/
${
path
}
`
;
}
return
null
;
}
function
extractRemote
(
url
:
string
,
stripEndingDotGit
:
boolean
):
string
|
null
{
if
(
url
.
indexOf
(
'
://
'
)
===
-
1
)
{
const
match
=
url
.
match
(
SshUrlMatcher
);
if
(
match
)
{
return
normalizeRemote
(
match
[
2
],
match
[
3
],
stripEndingDotGit
);
}
}
try
{
const
uri
=
URI
.
parse
(
url
);
if
(
uri
.
authority
)
{
return
normalizeRemote
(
stripPort
(
uri
.
authority
),
uri
.
path
,
stripEndingDotGit
);
}
}
catch
(
e
)
{
// ignore invalid URIs
}
return
null
;
}
export
function
getRemotes
(
text
:
string
,
stripEndingDotGit
:
boolean
=
false
):
string
[]
{
const
remotes
:
string
[]
=
[];
let
match
:
RegExpExecArray
|
null
;
while
(
match
=
RemoteMatcher
.
exec
(
text
))
{
const
remote
=
extractRemote
(
match
[
1
],
stripEndingDotGit
);
if
(
remote
)
{
remotes
.
push
(
remote
);
}
}
return
remotes
;
}
import
{
getRemotes
,
SecondLevelDomainWhitelist
,
getDomainsOfRemotes
}
from
'
vs/platform/extensionManagement/common/configRemotes
'
;
export
function
getHashedRemotesFromConfig
(
text
:
string
,
stripEndingDotGit
:
boolean
=
false
):
string
[]
{
return
getRemotes
(
text
,
stripEndingDotGit
).
map
(
r
=>
{
...
...
src/vs/workbench/contrib/tags/test/electron-browser/workspaceTags.test.ts
浏览文件 @
5b33ba2a
...
...
@@ -5,7 +5,7 @@
import
*
as
assert
from
'
assert
'
;
import
*
as
crypto
from
'
crypto
'
;
import
{
get
DomainsOfRemotes
,
getRemotes
,
get
HashedRemotesFromConfig
}
from
'
vs/workbench/contrib/tags/electron-browser/workspaceTags
'
;
import
{
getHashedRemotesFromConfig
}
from
'
vs/workbench/contrib/tags/electron-browser/workspaceTags
'
;
function
hash
(
value
:
string
):
string
{
return
crypto
.
createHash
(
'
sha1
'
).
update
(
value
.
toString
()).
digest
(
'
hex
'
);
...
...
@@ -13,119 +13,6 @@ function hash(value: string): string {
suite
(
'
Telemetry - WorkspaceTags
'
,
()
=>
{
const
whitelist
=
[
'
github.com
'
,
'
github2.com
'
,
'
github3.com
'
,
'
example.com
'
,
'
example2.com
'
,
'
example3.com
'
,
'
server.org
'
,
'
server2.org
'
,
];
test
(
'
HTTPS remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
),
whitelist
),
[
'
github.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
),
whitelist
),
[
'
example.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
),
whitelist
),
[
'
github2.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
),
whitelist
),
[
'
github3.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
),
whitelist
),
[
'
example2.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
),
whitelist
),
[
'
example3.com
'
]);
});
test
(
'
SSH remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
),
whitelist
),
[
'
server.org
'
]);
});
test
(
'
SCP-like remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
),
whitelist
),
[
'
github.com
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
user@git.server.org:project.git
'
),
whitelist
),
[
'
server.org
'
]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
git.server2.org:project.git
'
),
whitelist
),
[
'
server2.org
'
]);
});
test
(
'
Local remotes
'
,
function
()
{
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
/opt/git/project.git
'
),
whitelist
),
[]);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
remote
(
'
file:///opt/git/project.git
'
),
whitelist
),
[]);
});
test
(
'
Multiple remotes
'
,
function
()
{
const
config
=
[
'
https://github.com/Microsoft/vscode.git
'
,
'
https://git.example.com/gitproject.git
'
].
map
(
remote
).
join
(
''
);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
config
,
whitelist
).
sort
(),
[
'
example.com
'
,
'
github.com
'
]);
});
test
(
'
Whitelisting
'
,
()
=>
{
const
config
=
[
'
https://github.com/Microsoft/vscode.git
'
,
'
https://git.foobar.com/gitproject.git
'
].
map
(
remote
).
join
(
''
);
assert
.
deepStrictEqual
(
getDomainsOfRemotes
(
config
,
whitelist
).
sort
(),
[
'
aaaaaa.aaa
'
,
'
github.com
'
]);
});
test
(
'
HTTPS remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
)),
[
'
github.com/Microsoft/vscode.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
)),
[
'
git.example.com/gitproject.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
)),
[
'
github2.com/username/repository.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
)),
[
'
github3.com/username/repository.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
)),
[
'
example2.com/username/repository.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
)),
[
'
example3.com/username/repository.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
),
true
),
[
'
github.com/Microsoft/vscode
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
),
true
),
[
'
git.example.com/gitproject
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
),
true
),
[
'
github2.com/username/repository
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
),
true
),
[
'
github3.com/username/repository
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
),
true
),
[
'
example2.com/username/repository
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
),
true
),
[
'
example3.com/username/repository
'
]);
// Compare Striped .git with no .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode.git
'
),
true
),
getRemotes
(
remote
(
'
https://github.com/Microsoft/vscode
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://git.example.com/gitproject.git
'
),
true
),
getRemotes
(
remote
(
'
https://git.example.com/gitproject
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username@github2.com/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://username@github2.com/username/repository
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://username:password@github3.com/username/repository
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://username:password@example2.com:1234/username/repository
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository.git
'
),
true
),
getRemotes
(
remote
(
'
https://example3.com:1234/username/repository
'
)));
});
test
(
'
SSH remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
)),
[
'
git.server.org/project.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
),
true
),
[
'
git.server.org/project
'
]);
// Compare Striped .git with no .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
ssh://user@git.server.org/project.git
'
),
true
),
getRemotes
(
remote
(
'
ssh://user@git.server.org/project
'
)));
});
test
(
'
SCP-like remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
)),
[
'
github.com/Microsoft/vscode.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
user@git.server.org:project.git
'
)),
[
'
git.server.org/project.git
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git.server2.org:project.git
'
)),
[
'
git.server2.org/project.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
),
true
),
[
'
github.com/Microsoft/vscode
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
user@git.server.org:project.git
'
),
true
),
[
'
git.server.org/project
'
]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git.server2.org:project.git
'
),
true
),
[
'
git.server2.org/project
'
]);
// Compare Striped .git with no .git
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode.git
'
),
true
),
getRemotes
(
remote
(
'
git@github.com:Microsoft/vscode
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
user@git.server.org:project.git
'
),
true
),
getRemotes
(
remote
(
'
user@git.server.org:project
'
)));
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
git.server2.org:project.git
'
),
true
),
getRemotes
(
remote
(
'
git.server2.org:project
'
)));
});
test
(
'
Local remotes to be hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
/opt/git/project.git
'
)),
[]);
assert
.
deepStrictEqual
(
getRemotes
(
remote
(
'
file:///opt/git/project.git
'
)),
[]);
});
test
(
'
Multiple remotes to be hashed
'
,
function
()
{
const
config
=
[
'
https://github.com/Microsoft/vscode.git
'
,
'
https://git.example.com/gitproject.git
'
].
map
(
remote
).
join
(
'
'
);
assert
.
deepStrictEqual
(
getRemotes
(
config
),
[
'
github.com/Microsoft/vscode.git
'
,
'
git.example.com/gitproject.git
'
]);
// Strip .git
assert
.
deepStrictEqual
(
getRemotes
(
config
,
true
),
[
'
github.com/Microsoft/vscode
'
,
'
git.example.com/gitproject
'
]);
// Compare Striped .git with no .git
const
noDotGitConfig
=
[
'
https://github.com/Microsoft/vscode
'
,
'
https://git.example.com/gitproject
'
].
map
(
remote
).
join
(
'
'
);
assert
.
deepStrictEqual
(
getRemotes
(
config
,
true
),
getRemotes
(
noDotGitConfig
));
});
test
(
'
Single remote hashed
'
,
function
()
{
assert
.
deepStrictEqual
(
getHashedRemotesFromConfig
(
remote
(
'
https://username:password@github3.com/username/repository.git
'
)),
[
hash
(
'
github3.com/username/repository.git
'
)]);
assert
.
deepStrictEqual
(
getHashedRemotesFromConfig
(
remote
(
'
ssh://user@git.server.org/project.git
'
)),
[
hash
(
'
git.server.org/project.git
'
)]);
...
...
src/vs/workbench/services/extensionManagement/common/extensionManagement.ts
浏览文件 @
5b33ba2a
...
...
@@ -85,11 +85,12 @@ export type RecommendationChangeNotification = {
};
export
type
DynamicRecommendation
=
'
dynamic
'
;
export
type
ConfigRecommendation
=
'
config
'
;
export
type
ExecutableRecommendation
=
'
executable
'
;
export
type
CachedRecommendation
=
'
cached
'
;
export
type
ApplicationRecommendation
=
'
application
'
;
export
type
ExperimentalRecommendation
=
'
experimental
'
;
export
type
ExtensionRecommendationSource
=
IWorkspace
|
IWorkspaceFolder
|
URI
|
DynamicRecommendation
|
ExecutableRecommendation
|
CachedRecommendation
|
ApplicationRecommendation
|
ExperimentalRecommendation
;
export
type
ExtensionRecommendationSource
=
IWorkspace
|
IWorkspaceFolder
|
URI
|
DynamicRecommendation
|
ExecutableRecommendation
|
CachedRecommendation
|
ApplicationRecommendation
|
ExperimentalRecommendation
|
ConfigRecommendation
;
export
interface
IExtensionRecommendation
{
extensionId
:
string
;
...
...
@@ -100,6 +101,7 @@ export const enum ExtensionRecommendationReason {
Workspace
,
File
,
Executable
,
WorkspaceConfig
,
DynamicWorkspace
,
Experimental
,
Application
,
...
...
@@ -117,6 +119,7 @@ export interface IExtensionRecommendationsService {
getAllRecommendationsWithReason
():
IStringDictionary
<
IExtensionRecommendationReson
>
;
getFileBasedRecommendations
():
IExtensionRecommendation
[];
getConfigBasedRecommendations
():
Promise
<
IExtensionRecommendation
[]
>
;
getOtherRecommendations
():
Promise
<
IExtensionRecommendation
[]
>
;
getWorkspaceRecommendations
():
Promise
<
IExtensionRecommendation
[]
>
;
getKeymapRecommendations
():
IExtensionRecommendation
[];
...
...
src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts
已删除
100644 → 0
浏览文件 @
fa4c3ae9
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
IExtensionTipsService
,
IExecutableBasedExtensionTip
,
IWorkspaceTips
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
class
WebExtensionTipsService
implements
IExtensionTipsService
{
_serviceBrand
:
any
;
constructor
()
{
}
async
getImportantExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
{
return
[];
}
async
getOtherExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
{
return
[];
}
async
getAllWorkspacesTips
():
Promise
<
IWorkspaceTips
[]
>
{
return
[];
}
}
registerSingleton
(
IExtensionTipsService
,
WebExtensionTipsService
);
src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts
浏览文件 @
5b33ba2a
...
...
@@ -6,7 +6,8 @@
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
ISharedProcessService
}
from
'
vs/platform/ipc/electron-browser/sharedProcessService
'
;
import
{
IChannel
}
from
'
vs/base/parts/ipc/common/ipc
'
;
import
{
IExtensionTipsService
,
IExecutableBasedExtensionTip
,
IWorkspaceTips
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
IExtensionTipsService
,
IExecutableBasedExtensionTip
,
IWorkspaceTips
,
IConfigBasedExtensionTip
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
class
NativeExtensionTipsService
implements
IExtensionTipsService
{
...
...
@@ -20,6 +21,10 @@ class NativeExtensionTipsService implements IExtensionTipsService {
this
.
channel
=
sharedProcessService
.
getChannel
(
'
extensionTipsService
'
);
}
getConfigBasedTips
(
folder
:
URI
):
Promise
<
IConfigBasedExtensionTip
[]
>
{
return
this
.
channel
.
call
<
IConfigBasedExtensionTip
[]
>
(
'
getConfigBasedTips
'
,
[
folder
]);
}
getImportantExecutableBasedTips
():
Promise
<
IExecutableBasedExtensionTip
[]
>
{
return
this
.
channel
.
call
<
IExecutableBasedExtensionTip
[]
>
(
'
getImportantExecutableBasedTips
'
);
}
...
...
src/vs/workbench/workbench.web.main.ts
浏览文件 @
5b33ba2a
...
...
@@ -34,7 +34,6 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
import
'
vs/workbench/services/keybinding/browser/keymapService
'
;
import
'
vs/workbench/services/extensions/browser/extensionService
'
;
import
'
vs/workbench/services/extensionManagement/common/extensionManagementServerService
'
;
import
'
vs/workbench/services/extensionManagement/common/extensionTipsService
'
;
import
'
vs/workbench/services/telemetry/browser/telemetryService
'
;
import
'
vs/workbench/services/configurationResolver/browser/configurationResolverService
'
;
import
'
vs/workbench/services/credentials/browser/credentialsService
'
;
...
...
@@ -58,7 +57,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import
{
ContextMenuService
}
from
'
vs/platform/contextview/browser/contextMenuService
'
;
import
{
IBackupFileService
}
from
'
vs/workbench/services/backup/common/backup
'
;
import
{
BackupFileService
}
from
'
vs/workbench/services/backup/common/backupFileService
'
;
import
{
IExtensionManagementService
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
IExtensionManagementService
,
IExtensionTipsService
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
ExtensionTipsService
}
from
'
vs/platform/extensionManagement/common/extensionTipsService
'
;
import
{
ExtensionManagementService
}
from
'
vs/workbench/services/extensionManagement/common/extensionManagementService
'
;
import
{
ITunnelService
}
from
'
vs/platform/remote/common/tunnel
'
;
import
{
TunnelService
}
from
'
vs/workbench/services/remote/common/tunnelService
'
;
...
...
@@ -92,6 +92,7 @@ registerSingleton(IAuthenticationTokenService, AuthenticationTokenService);
registerSingleton
(
IUserDataAutoSyncService
,
UserDataAutoSyncService
);
registerSingleton
(
IUserDataSyncService
,
UserDataSyncService
);
registerSingleton
(
ITitleService
,
TitlebarPart
);
registerSingleton
(
IExtensionTipsService
,
ExtensionTipsService
);
//#endregion
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录