Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
掘金者说
vscode
提交
a3ec0455
V
vscode
项目概览
掘金者说
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
a3ec0455
编写于
7月 12, 2019
作者:
S
Sandeep Somavarapu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Fix #69111
上级
9943a447
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
352 addition
and
437 deletion
+352
-437
src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
...orkbench/contrib/preferences/browser/preferencesSearch.ts
+351
-5
src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts
...contrib/preferences/electron-browser/preferencesSearch.ts
+0
-431
src/vs/workbench/workbench.main.ts
src/vs/workbench/workbench.main.ts
+1
-1
未找到文件。
src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
浏览文件 @
a3ec0455
...
...
@@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ISettingsEditorModel
,
ISetting
,
ISettingsGroup
,
I
SearchResult
,
IGroupFilter
}
from
'
vs/workbench/services/preferences/common/preferences
'
;
import
{
ISettingsEditorModel
,
ISetting
,
ISettingsGroup
,
I
FilterMetadata
,
ISearchResult
,
IGroupFilter
,
ISettingMatcher
,
IScoredResults
,
ISettingMatch
,
IRemoteSetting
,
IExtensionSetting
}
from
'
vs/workbench/services/preferences/common/preferences
'
;
import
{
IRange
}
from
'
vs/editor/common/core/range
'
;
import
{
distinct
}
from
'
vs/base/common/arrays
'
;
import
{
distinct
,
top
}
from
'
vs/base/common/arrays
'
;
import
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
IJSONSchema
}
from
'
vs/base/common/jsonSchema
'
;
import
{
Registry
}
from
'
vs/platform/registry/common/platform
'
;
...
...
@@ -13,8 +13,17 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co
import
{
IMatch
,
or
,
matchesContiguousSubString
,
matchesPrefix
,
matchesCamelCase
,
matchesWords
}
from
'
vs/base/common/filters
'
;
import
{
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
IPreferencesSearchService
,
ISearchProvider
}
from
'
vs/workbench/contrib/preferences/common/preferences
'
;
import
{
IPreferencesSearchService
,
ISearchProvider
,
IWorkbenchSettingsConfiguration
}
from
'
vs/workbench/contrib/preferences/common/preferences
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
import
{
IRequestService
,
asJson
}
from
'
vs/platform/request/common/request
'
;
import
{
IExtensionManagementService
,
ILocalExtension
,
IExtensionEnablementService
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
ILogService
}
from
'
vs/platform/log/common/log
'
;
import
{
CancellationToken
}
from
'
vs/base/common/cancellation
'
;
import
{
canceled
}
from
'
vs/base/common/errors
'
;
import
{
ExtensionType
}
from
'
vs/platform/extensions/common/extensions
'
;
import
{
nullRange
}
from
'
vs/workbench/services/preferences/common/preferencesModels
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
IStringDictionary
}
from
'
vs/base/common/collections
'
;
export
interface
IEndpointDetails
{
urlBase
?:
string
;
...
...
@@ -24,14 +33,58 @@ export interface IEndpointDetails {
export
class
PreferencesSearchService
extends
Disposable
implements
IPreferencesSearchService
{
_serviceBrand
:
any
;
private
_installedExtensions
:
Promise
<
ILocalExtension
[]
>
;
constructor
(
@
IInstantiationService
protected
readonly
instantiationService
:
IInstantiationService
,
@
IInstantiationService
private
readonly
instantiationService
:
IInstantiationService
,
@
IConfigurationService
private
readonly
configurationService
:
IConfigurationService
,
@
IEnvironmentService
private
readonly
environmentService
:
IEnvironmentService
,
@
IExtensionManagementService
private
readonly
extensionManagementService
:
IExtensionManagementService
,
@
IExtensionEnablementService
private
readonly
extensionEnablementService
:
IExtensionEnablementService
)
{
super
();
// This request goes to the shared process but results won't change during a window's lifetime, so cache the results.
this
.
_installedExtensions
=
this
.
extensionManagementService
.
getInstalled
(
ExtensionType
.
User
).
then
(
exts
=>
{
// Filter to enabled extensions that have settings
return
exts
.
filter
(
ext
=>
this
.
extensionEnablementService
.
isEnabled
(
ext
))
.
filter
(
ext
=>
ext
.
manifest
&&
ext
.
manifest
.
contributes
&&
ext
.
manifest
.
contributes
.
configuration
)
.
filter
(
ext
=>
!!
ext
.
identifier
.
uuid
);
});
}
private
get
remoteSearchAllowed
():
boolean
{
const
workbenchSettings
=
this
.
configurationService
.
getValue
<
IWorkbenchSettingsConfiguration
>
().
workbench
.
settings
;
if
(
!
workbenchSettings
.
enableNaturalLanguageSearch
)
{
return
false
;
}
return
!!
this
.
_endpoint
.
urlBase
;
}
private
get
_endpoint
():
IEndpointDetails
{
const
workbenchSettings
=
this
.
configurationService
.
getValue
<
IWorkbenchSettingsConfiguration
>
().
workbench
.
settings
;
if
(
workbenchSettings
.
naturalLanguageSearchEndpoint
)
{
return
{
urlBase
:
workbenchSettings
.
naturalLanguageSearchEndpoint
,
key
:
workbenchSettings
.
naturalLanguageSearchKey
};
}
else
{
return
{
urlBase
:
this
.
environmentService
.
settingsSearchUrl
};
}
}
getRemoteSearchProvider
(
filter
:
string
,
newExtensionsOnly
=
false
):
ISearchProvider
|
undefined
{
return
undefined
;
const
opts
:
IRemoteSearchProviderOptions
=
{
filter
,
newExtensionsOnly
,
endpoint
:
this
.
_endpoint
};
return
this
.
remoteSearchAllowed
?
this
.
instantiationService
.
createInstance
(
RemoteSearchProvider
,
opts
,
this
.
_installedExtensions
)
:
undefined
;
}
getLocalSearchProvider
(
filter
:
string
):
LocalSearchProvider
{
...
...
@@ -93,6 +146,299 @@ export class LocalSearchProvider implements ISearchProvider {
}
}
interface
IRemoteSearchProviderOptions
{
filter
:
string
;
endpoint
:
IEndpointDetails
;
newExtensionsOnly
:
boolean
;
}
interface
IBingRequestDetails
{
url
:
string
;
body
?:
string
;
hasMoreFilters
?:
boolean
;
extensions
?:
ILocalExtension
[];
}
class
RemoteSearchProvider
implements
ISearchProvider
{
// Must keep extension filter size under 8kb. 42 filters puts us there.
private
static
readonly
MAX_REQUEST_FILTERS
=
42
;
private
static
readonly
MAX_REQUESTS
=
10
;
private
static
readonly
NEW_EXTENSIONS_MIN_SCORE
=
1
;
private
_remoteSearchP
:
Promise
<
IFilterMetadata
|
null
>
;
constructor
(
private
options
:
IRemoteSearchProviderOptions
,
private
installedExtensions
:
Promise
<
ILocalExtension
[]
>
,
@
IEnvironmentService
private
readonly
environmentService
:
IEnvironmentService
,
@
IRequestService
private
readonly
requestService
:
IRequestService
,
@
ILogService
private
readonly
logService
:
ILogService
)
{
this
.
_remoteSearchP
=
this
.
options
.
filter
?
Promise
.
resolve
(
this
.
getSettingsForFilter
(
this
.
options
.
filter
))
:
Promise
.
resolve
(
null
);
}
searchModel
(
preferencesModel
:
ISettingsEditorModel
,
token
?:
CancellationToken
):
Promise
<
ISearchResult
|
null
>
{
return
this
.
_remoteSearchP
.
then
<
ISearchResult
|
null
>
((
remoteResult
)
=>
{
if
(
!
remoteResult
)
{
return
null
;
}
if
(
token
&&
token
.
isCancellationRequested
)
{
throw
canceled
();
}
const
resultKeys
=
Object
.
keys
(
remoteResult
.
scoredResults
);
const
highScoreKey
=
top
(
resultKeys
,
(
a
,
b
)
=>
remoteResult
.
scoredResults
[
b
].
score
-
remoteResult
.
scoredResults
[
a
].
score
,
1
)[
0
];
const
highScore
=
highScoreKey
?
remoteResult
.
scoredResults
[
highScoreKey
].
score
:
0
;
const
minScore
=
highScore
/
5
;
if
(
this
.
options
.
newExtensionsOnly
)
{
return
this
.
installedExtensions
.
then
(
installedExtensions
=>
{
const
newExtsMinScore
=
Math
.
max
(
RemoteSearchProvider
.
NEW_EXTENSIONS_MIN_SCORE
,
minScore
);
const
passingScoreKeys
=
resultKeys
.
filter
(
k
=>
{
const
result
=
remoteResult
.
scoredResults
[
k
];
const
resultExtId
=
(
result
.
extensionPublisher
+
'
.
'
+
result
.
extensionName
).
toLowerCase
();
return
!
installedExtensions
.
some
(
ext
=>
ext
.
identifier
.
id
.
toLowerCase
()
===
resultExtId
);
})
.
filter
(
k
=>
remoteResult
.
scoredResults
[
k
].
score
>=
newExtsMinScore
);
const
filterMatches
:
ISettingMatch
[]
=
passingScoreKeys
.
map
(
k
=>
{
const
remoteSetting
=
remoteResult
.
scoredResults
[
k
];
const
setting
=
remoteSettingToISetting
(
remoteSetting
);
return
<
ISettingMatch
>
{
setting
,
score
:
remoteSetting
.
score
,
matches
:
[]
// TODO
};
});
return
<
ISearchResult
>
{
filterMatches
,
metadata
:
remoteResult
};
});
}
else
{
const
settingMatcher
=
this
.
getRemoteSettingMatcher
(
remoteResult
.
scoredResults
,
minScore
,
preferencesModel
);
const
filterMatches
=
preferencesModel
.
filterSettings
(
this
.
options
.
filter
,
group
=>
null
,
settingMatcher
);
return
<
ISearchResult
>
{
filterMatches
,
metadata
:
remoteResult
};
}
});
}
private
async
getSettingsForFilter
(
filter
:
string
):
Promise
<
IFilterMetadata
>
{
const
allRequestDetails
:
IBingRequestDetails
[]
=
[];
// Only send MAX_REQUESTS requests in total just to keep it sane
for
(
let
i
=
0
;
i
<
RemoteSearchProvider
.
MAX_REQUESTS
;
i
++
)
{
const
details
=
await
this
.
prepareRequest
(
filter
,
i
);
allRequestDetails
.
push
(
details
);
if
(
!
details
.
hasMoreFilters
)
{
break
;
}
}
return
Promise
.
all
(
allRequestDetails
.
map
(
details
=>
this
.
getSettingsFromBing
(
details
))).
then
(
allResponses
=>
{
// Merge all IFilterMetadata
const
metadata
=
allResponses
[
0
];
metadata
.
requestCount
=
1
;
for
(
const
response
of
allResponses
.
slice
(
1
))
{
metadata
.
requestCount
++
;
metadata
.
scoredResults
=
{
...
metadata
.
scoredResults
,
...
response
.
scoredResults
};
}
return
metadata
;
});
}
private
getSettingsFromBing
(
details
:
IBingRequestDetails
):
Promise
<
IFilterMetadata
>
{
this
.
logService
.
debug
(
`Searching settings via
${
details
.
url
}
`
);
if
(
details
.
body
)
{
this
.
logService
.
debug
(
`Body:
${
details
.
body
}
`
);
}
const
requestType
=
details
.
body
?
'
post
'
:
'
get
'
;
const
headers
:
IStringDictionary
<
string
>
=
{
'
User-Agent
'
:
'
request
'
,
'
Content-Type
'
:
'
application/json; charset=utf-8
'
,
};
if
(
this
.
options
.
endpoint
.
key
)
{
headers
[
'
api-key
'
]
=
this
.
options
.
endpoint
.
key
;
}
const
start
=
Date
.
now
();
return
this
.
requestService
.
request
({
type
:
requestType
,
url
:
details
.
url
,
data
:
details
.
body
,
headers
,
timeout
:
5000
},
CancellationToken
.
None
).
then
(
context
=>
{
if
(
typeof
context
.
res
.
statusCode
===
'
number
'
&&
context
.
res
.
statusCode
>=
300
)
{
throw
new
Error
(
`
${
JSON
.
stringify
(
details
)}
returned status code:
${
context
.
res
.
statusCode
}
`
);
}
return
asJson
(
context
);
}).
then
((
result
:
any
)
=>
{
const
timestamp
=
Date
.
now
();
const
duration
=
timestamp
-
start
;
const
remoteSettings
:
IRemoteSetting
[]
=
(
result
.
value
||
[])
.
map
((
r
:
any
)
=>
{
const
key
=
JSON
.
parse
(
r
.
setting
||
r
.
Setting
);
const
packageId
=
r
[
'
packageid
'
];
const
id
=
getSettingKey
(
key
,
packageId
);
const
value
=
r
[
'
value
'
];
const
defaultValue
=
value
?
JSON
.
parse
(
value
)
:
value
;
const
packageName
=
r
[
'
packagename
'
];
let
extensionName
:
string
|
undefined
;
let
extensionPublisher
:
string
|
undefined
;
if
(
packageName
&&
packageName
.
indexOf
(
'
##
'
)
>=
0
)
{
[
extensionPublisher
,
extensionName
]
=
packageName
.
split
(
'
##
'
);
}
return
<
IRemoteSetting
>
{
key
,
id
,
defaultValue
,
score
:
r
[
'
@search.score
'
],
description
:
JSON
.
parse
(
r
[
'
details
'
]),
packageId
,
extensionName
,
extensionPublisher
};
});
const
scoredResults
=
Object
.
create
(
null
);
remoteSettings
.
forEach
(
s
=>
{
scoredResults
[
s
.
id
]
=
s
;
});
return
<
IFilterMetadata
>
{
requestUrl
:
details
.
url
,
requestBody
:
details
.
body
,
duration
,
timestamp
,
scoredResults
,
context
:
result
[
'
@odata.context
'
]
};
});
}
private
getRemoteSettingMatcher
(
scoredResults
:
IScoredResults
,
minScore
:
number
,
preferencesModel
:
ISettingsEditorModel
):
ISettingMatcher
{
return
(
setting
:
ISetting
,
group
:
ISettingsGroup
)
=>
{
const
remoteSetting
=
scoredResults
[
getSettingKey
(
setting
.
key
,
group
.
id
)]
||
// extension setting
scoredResults
[
getSettingKey
(
setting
.
key
,
'
core
'
)]
||
// core setting
scoredResults
[
getSettingKey
(
setting
.
key
)];
// core setting from original prod endpoint
if
(
remoteSetting
&&
remoteSetting
.
score
>=
minScore
)
{
const
settingMatches
=
new
SettingMatches
(
this
.
options
.
filter
,
setting
,
false
,
true
,
(
filter
,
setting
)
=>
preferencesModel
.
findValueMatches
(
filter
,
setting
)).
matches
;
return
{
matches
:
settingMatches
,
score
:
remoteSetting
.
score
};
}
return
null
;
};
}
private
async
prepareRequest
(
query
:
string
,
filterPage
=
0
):
Promise
<
IBingRequestDetails
>
{
const
verbatimQuery
=
query
;
query
=
escapeSpecialChars
(
query
);
const
boost
=
10
;
const
boostedQuery
=
`(
${
query
}
)^
${
boost
}
`
;
// Appending Fuzzy after each word.
query
=
query
.
replace
(
/
\
+/g
,
'
~
'
)
+
'
~
'
;
const
encodedQuery
=
encodeURIComponent
(
boostedQuery
+
'
||
'
+
query
);
let
url
=
`
${
this
.
options
.
endpoint
.
urlBase
}
`
;
if
(
this
.
options
.
endpoint
.
key
)
{
url
+=
`
${
API_VERSION
}
&
${
QUERY_TYPE
}
`
;
}
const
extensions
=
await
this
.
installedExtensions
;
const
filters
=
this
.
options
.
newExtensionsOnly
?
[
`diminish eq 'latest'`
]
:
this
.
getVersionFilters
(
extensions
,
this
.
environmentService
.
settingsSearchBuildId
);
const
filterStr
=
filters
.
slice
(
filterPage
*
RemoteSearchProvider
.
MAX_REQUEST_FILTERS
,
(
filterPage
+
1
)
*
RemoteSearchProvider
.
MAX_REQUEST_FILTERS
)
.
join
(
'
or
'
);
const
hasMoreFilters
=
filters
.
length
>
(
filterPage
+
1
)
*
RemoteSearchProvider
.
MAX_REQUEST_FILTERS
;
const
body
=
JSON
.
stringify
({
query
:
encodedQuery
,
filters
:
encodeURIComponent
(
filterStr
),
rawQuery
:
encodeURIComponent
(
verbatimQuery
)
});
return
{
url
,
body
,
hasMoreFilters
};
}
private
getVersionFilters
(
exts
:
ILocalExtension
[],
buildNumber
?:
number
):
string
[]
{
// Only search extensions that contribute settings
const
filters
=
exts
.
filter
(
ext
=>
ext
.
manifest
.
contributes
&&
ext
.
manifest
.
contributes
.
configuration
)
.
map
(
ext
=>
this
.
getExtensionFilter
(
ext
));
if
(
buildNumber
)
{
filters
.
push
(
`(packageid eq 'core' and startbuildno le '
${
buildNumber
}
' and endbuildno ge '
${
buildNumber
}
')`
);
}
return
filters
;
}
private
getExtensionFilter
(
ext
:
ILocalExtension
):
string
{
const
uuid
=
ext
.
identifier
.
uuid
;
const
versionString
=
ext
.
manifest
.
version
.
split
(
'
.
'
)
.
map
(
versionPart
=>
strings
.
pad
(
<
any
>
versionPart
,
10
))
.
join
(
''
);
return
`(packageid eq '
${
uuid
}
' and startbuildno le '
${
versionString
}
' and endbuildno ge '
${
versionString
}
')`
;
}
}
function
getSettingKey
(
name
:
string
,
packageId
?:
string
):
string
{
return
packageId
?
packageId
+
'
##
'
+
name
:
name
;
}
const
API_VERSION
=
'
api-version=2016-09-01-Preview
'
;
const
QUERY_TYPE
=
'
querytype=full
'
;
function
escapeSpecialChars
(
query
:
string
):
string
{
return
query
.
replace
(
/
\.
/g
,
'
'
)
.
replace
(
/
[\\/
+
\-
&|!"~*?:(){}
\[\]\^]
/g
,
'
\\
$&
'
)
.
replace
(
/ /g
,
'
'
)
// collapse spaces
.
trim
();
}
function
remoteSettingToISetting
(
remoteSetting
:
IRemoteSetting
):
IExtensionSetting
{
return
{
description
:
remoteSetting
.
description
.
split
(
'
\n
'
),
descriptionIsMarkdown
:
false
,
descriptionRanges
:
[],
key
:
remoteSetting
.
key
,
keyRange
:
nullRange
,
value
:
remoteSetting
.
defaultValue
,
range
:
nullRange
,
valueRange
:
nullRange
,
overrides
:
[],
extensionName
:
remoteSetting
.
extensionName
,
extensionPublisher
:
remoteSetting
.
extensionPublisher
};
}
export
class
SettingMatches
{
private
readonly
descriptionMatchingWords
:
Map
<
string
,
IRange
[]
>
=
new
Map
<
string
,
IRange
[]
>
();
...
...
src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts
已删除
100644 → 0
浏览文件 @
9943a447
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ISettingsEditorModel
,
ISetting
,
ISettingsGroup
,
IFilterMetadata
,
ISearchResult
,
IGroupFilter
,
ISettingMatcher
,
IScoredResults
,
ISettingMatch
,
IRemoteSetting
,
IExtensionSetting
}
from
'
vs/workbench/services/preferences/common/preferences
'
;
import
{
top
}
from
'
vs/base/common/arrays
'
;
import
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
import
{
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
IRequestService
,
asJson
}
from
'
vs/platform/request/common/request
'
;
import
{
IExtensionManagementService
,
ILocalExtension
,
IExtensionEnablementService
}
from
'
vs/platform/extensionManagement/common/extensionManagement
'
;
import
{
ILogService
}
from
'
vs/platform/log/common/log
'
;
import
{
IPreferencesSearchService
,
ISearchProvider
,
IWorkbenchSettingsConfiguration
}
from
'
vs/workbench/contrib/preferences/common/preferences
'
;
import
{
CancellationToken
}
from
'
vs/base/common/cancellation
'
;
import
{
canceled
}
from
'
vs/base/common/errors
'
;
import
{
ExtensionType
}
from
'
vs/platform/extensions/common/extensions
'
;
import
{
nullRange
}
from
'
vs/workbench/services/preferences/common/preferencesModels
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
PreferencesSearchService
as
LocalPreferencesSearchService
,
SettingMatches
}
from
'
vs/workbench/contrib/preferences/browser/preferencesSearch
'
;
import
{
IStringDictionary
}
from
'
vs/base/common/collections
'
;
export
interface
IEndpointDetails
{
urlBase
?:
string
;
key
?:
string
;
}
export
class
PreferencesSearchService
extends
LocalPreferencesSearchService
implements
IPreferencesSearchService
{
_serviceBrand
:
any
;
private
_installedExtensions
:
Promise
<
ILocalExtension
[]
>
;
constructor
(
@
IInstantiationService
instantiationService
:
IInstantiationService
,
@
IConfigurationService
private
readonly
configurationService
:
IConfigurationService
,
@
IEnvironmentService
private
readonly
environmentService
:
IEnvironmentService
,
@
IExtensionManagementService
private
readonly
extensionManagementService
:
IExtensionManagementService
,
@
IExtensionEnablementService
private
readonly
extensionEnablementService
:
IExtensionEnablementService
)
{
super
(
instantiationService
);
// This request goes to the shared process but results won't change during a window's lifetime, so cache the results.
this
.
_installedExtensions
=
this
.
extensionManagementService
.
getInstalled
(
ExtensionType
.
User
).
then
(
exts
=>
{
// Filter to enabled extensions that have settings
return
exts
.
filter
(
ext
=>
this
.
extensionEnablementService
.
isEnabled
(
ext
))
.
filter
(
ext
=>
ext
.
manifest
&&
ext
.
manifest
.
contributes
&&
ext
.
manifest
.
contributes
.
configuration
)
.
filter
(
ext
=>
!!
ext
.
identifier
.
uuid
);
});
}
private
get
remoteSearchAllowed
():
boolean
{
const
workbenchSettings
=
this
.
configurationService
.
getValue
<
IWorkbenchSettingsConfiguration
>
().
workbench
.
settings
;
if
(
!
workbenchSettings
.
enableNaturalLanguageSearch
)
{
return
false
;
}
return
!!
this
.
_endpoint
.
urlBase
;
}
private
get
_endpoint
():
IEndpointDetails
{
const
workbenchSettings
=
this
.
configurationService
.
getValue
<
IWorkbenchSettingsConfiguration
>
().
workbench
.
settings
;
if
(
workbenchSettings
.
naturalLanguageSearchEndpoint
)
{
return
{
urlBase
:
workbenchSettings
.
naturalLanguageSearchEndpoint
,
key
:
workbenchSettings
.
naturalLanguageSearchKey
};
}
else
{
return
{
urlBase
:
this
.
environmentService
.
settingsSearchUrl
};
}
}
getRemoteSearchProvider
(
filter
:
string
,
newExtensionsOnly
=
false
):
ISearchProvider
|
undefined
{
const
opts
:
IRemoteSearchProviderOptions
=
{
filter
,
newExtensionsOnly
,
endpoint
:
this
.
_endpoint
};
return
this
.
remoteSearchAllowed
?
this
.
instantiationService
.
createInstance
(
RemoteSearchProvider
,
opts
,
this
.
_installedExtensions
)
:
undefined
;
}
}
export
class
LocalSearchProvider
implements
ISearchProvider
{
static
readonly
EXACT_MATCH_SCORE
=
10000
;
static
readonly
START_SCORE
=
1000
;
constructor
(
private
_filter
:
string
)
{
// Remove " and : which are likely to be copypasted as part of a setting name.
// Leave other special characters which the user might want to search for.
this
.
_filter
=
this
.
_filter
.
replace
(
/
[
":
]
/g
,
'
'
)
.
replace
(
/ /g
,
'
'
)
.
trim
();
}
searchModel
(
preferencesModel
:
ISettingsEditorModel
,
token
?:
CancellationToken
):
Promise
<
ISearchResult
|
null
>
{
if
(
!
this
.
_filter
)
{
return
Promise
.
resolve
(
null
);
}
let
orderedScore
=
LocalSearchProvider
.
START_SCORE
;
// Sort is not stable
const
settingMatcher
=
(
setting
:
ISetting
)
=>
{
const
matches
=
new
SettingMatches
(
this
.
_filter
,
setting
,
true
,
true
,
(
filter
,
setting
)
=>
preferencesModel
.
findValueMatches
(
filter
,
setting
)).
matches
;
const
score
=
this
.
_filter
===
setting
.
key
?
LocalSearchProvider
.
EXACT_MATCH_SCORE
:
orderedScore
--
;
return
matches
&&
matches
.
length
?
{
matches
,
score
}
:
null
;
};
const
filterMatches
=
preferencesModel
.
filterSettings
(
this
.
_filter
,
this
.
getGroupFilter
(
this
.
_filter
),
settingMatcher
);
if
(
filterMatches
[
0
]
&&
filterMatches
[
0
].
score
===
LocalSearchProvider
.
EXACT_MATCH_SCORE
)
{
return
Promise
.
resolve
({
filterMatches
:
filterMatches
.
slice
(
0
,
1
),
exactMatch
:
true
});
}
else
{
return
Promise
.
resolve
({
filterMatches
});
}
}
private
getGroupFilter
(
filter
:
string
):
IGroupFilter
{
const
regex
=
strings
.
createRegExp
(
filter
,
false
,
{
global
:
true
});
return
(
group
:
ISettingsGroup
)
=>
{
return
regex
.
test
(
group
.
title
);
};
}
}
interface
IRemoteSearchProviderOptions
{
filter
:
string
;
endpoint
:
IEndpointDetails
;
newExtensionsOnly
:
boolean
;
}
interface
IBingRequestDetails
{
url
:
string
;
body
?:
string
;
hasMoreFilters
?:
boolean
;
extensions
?:
ILocalExtension
[];
}
class
RemoteSearchProvider
implements
ISearchProvider
{
// Must keep extension filter size under 8kb. 42 filters puts us there.
private
static
readonly
MAX_REQUEST_FILTERS
=
42
;
private
static
readonly
MAX_REQUESTS
=
10
;
private
static
readonly
NEW_EXTENSIONS_MIN_SCORE
=
1
;
private
_remoteSearchP
:
Promise
<
IFilterMetadata
|
null
>
;
constructor
(
private
options
:
IRemoteSearchProviderOptions
,
private
installedExtensions
:
Promise
<
ILocalExtension
[]
>
,
@
IEnvironmentService
private
readonly
environmentService
:
IEnvironmentService
,
@
IRequestService
private
readonly
requestService
:
IRequestService
,
@
ILogService
private
readonly
logService
:
ILogService
)
{
this
.
_remoteSearchP
=
this
.
options
.
filter
?
Promise
.
resolve
(
this
.
getSettingsForFilter
(
this
.
options
.
filter
))
:
Promise
.
resolve
(
null
);
}
searchModel
(
preferencesModel
:
ISettingsEditorModel
,
token
?:
CancellationToken
):
Promise
<
ISearchResult
|
null
>
{
return
this
.
_remoteSearchP
.
then
<
ISearchResult
|
null
>
((
remoteResult
)
=>
{
if
(
!
remoteResult
)
{
return
null
;
}
if
(
token
&&
token
.
isCancellationRequested
)
{
throw
canceled
();
}
const
resultKeys
=
Object
.
keys
(
remoteResult
.
scoredResults
);
const
highScoreKey
=
top
(
resultKeys
,
(
a
,
b
)
=>
remoteResult
.
scoredResults
[
b
].
score
-
remoteResult
.
scoredResults
[
a
].
score
,
1
)[
0
];
const
highScore
=
highScoreKey
?
remoteResult
.
scoredResults
[
highScoreKey
].
score
:
0
;
const
minScore
=
highScore
/
5
;
if
(
this
.
options
.
newExtensionsOnly
)
{
return
this
.
installedExtensions
.
then
(
installedExtensions
=>
{
const
newExtsMinScore
=
Math
.
max
(
RemoteSearchProvider
.
NEW_EXTENSIONS_MIN_SCORE
,
minScore
);
const
passingScoreKeys
=
resultKeys
.
filter
(
k
=>
{
const
result
=
remoteResult
.
scoredResults
[
k
];
const
resultExtId
=
(
result
.
extensionPublisher
+
'
.
'
+
result
.
extensionName
).
toLowerCase
();
return
!
installedExtensions
.
some
(
ext
=>
ext
.
identifier
.
id
.
toLowerCase
()
===
resultExtId
);
})
.
filter
(
k
=>
remoteResult
.
scoredResults
[
k
].
score
>=
newExtsMinScore
);
const
filterMatches
:
ISettingMatch
[]
=
passingScoreKeys
.
map
(
k
=>
{
const
remoteSetting
=
remoteResult
.
scoredResults
[
k
];
const
setting
=
remoteSettingToISetting
(
remoteSetting
);
return
<
ISettingMatch
>
{
setting
,
score
:
remoteSetting
.
score
,
matches
:
[]
// TODO
};
});
return
<
ISearchResult
>
{
filterMatches
,
metadata
:
remoteResult
};
});
}
else
{
const
settingMatcher
=
this
.
getRemoteSettingMatcher
(
remoteResult
.
scoredResults
,
minScore
,
preferencesModel
);
const
filterMatches
=
preferencesModel
.
filterSettings
(
this
.
options
.
filter
,
group
=>
null
,
settingMatcher
);
return
<
ISearchResult
>
{
filterMatches
,
metadata
:
remoteResult
};
}
});
}
private
async
getSettingsForFilter
(
filter
:
string
):
Promise
<
IFilterMetadata
>
{
const
allRequestDetails
:
IBingRequestDetails
[]
=
[];
// Only send MAX_REQUESTS requests in total just to keep it sane
for
(
let
i
=
0
;
i
<
RemoteSearchProvider
.
MAX_REQUESTS
;
i
++
)
{
const
details
=
await
this
.
prepareRequest
(
filter
,
i
);
allRequestDetails
.
push
(
details
);
if
(
!
details
.
hasMoreFilters
)
{
break
;
}
}
return
Promise
.
all
(
allRequestDetails
.
map
(
details
=>
this
.
getSettingsFromBing
(
details
))).
then
(
allResponses
=>
{
// Merge all IFilterMetadata
const
metadata
=
allResponses
[
0
];
metadata
.
requestCount
=
1
;
for
(
const
response
of
allResponses
.
slice
(
1
))
{
metadata
.
requestCount
++
;
metadata
.
scoredResults
=
{
...
metadata
.
scoredResults
,
...
response
.
scoredResults
};
}
return
metadata
;
});
}
private
getSettingsFromBing
(
details
:
IBingRequestDetails
):
Promise
<
IFilterMetadata
>
{
this
.
logService
.
debug
(
`Searching settings via
${
details
.
url
}
`
);
if
(
details
.
body
)
{
this
.
logService
.
debug
(
`Body:
${
details
.
body
}
`
);
}
const
requestType
=
details
.
body
?
'
post
'
:
'
get
'
;
const
headers
:
IStringDictionary
<
string
>
=
{
'
User-Agent
'
:
'
request
'
,
'
Content-Type
'
:
'
application/json; charset=utf-8
'
,
};
if
(
this
.
options
.
endpoint
.
key
)
{
headers
[
'
api-key
'
]
=
this
.
options
.
endpoint
.
key
;
}
const
start
=
Date
.
now
();
return
this
.
requestService
.
request
({
type
:
requestType
,
url
:
details
.
url
,
data
:
details
.
body
,
headers
,
timeout
:
5000
},
CancellationToken
.
None
).
then
(
context
=>
{
if
(
typeof
context
.
res
.
statusCode
===
'
number
'
&&
context
.
res
.
statusCode
>=
300
)
{
throw
new
Error
(
`
${
JSON
.
stringify
(
details
)}
returned status code:
${
context
.
res
.
statusCode
}
`
);
}
return
asJson
(
context
);
}).
then
((
result
:
any
)
=>
{
const
timestamp
=
Date
.
now
();
const
duration
=
timestamp
-
start
;
const
remoteSettings
:
IRemoteSetting
[]
=
(
result
.
value
||
[])
.
map
((
r
:
any
)
=>
{
const
key
=
JSON
.
parse
(
r
.
setting
||
r
.
Setting
);
const
packageId
=
r
[
'
packageid
'
];
const
id
=
getSettingKey
(
key
,
packageId
);
const
value
=
r
[
'
value
'
];
const
defaultValue
=
value
?
JSON
.
parse
(
value
)
:
value
;
const
packageName
=
r
[
'
packagename
'
];
let
extensionName
:
string
|
undefined
;
let
extensionPublisher
:
string
|
undefined
;
if
(
packageName
&&
packageName
.
indexOf
(
'
##
'
)
>=
0
)
{
[
extensionPublisher
,
extensionName
]
=
packageName
.
split
(
'
##
'
);
}
return
<
IRemoteSetting
>
{
key
,
id
,
defaultValue
,
score
:
r
[
'
@search.score
'
],
description
:
JSON
.
parse
(
r
[
'
details
'
]),
packageId
,
extensionName
,
extensionPublisher
};
});
const
scoredResults
=
Object
.
create
(
null
);
remoteSettings
.
forEach
(
s
=>
{
scoredResults
[
s
.
id
]
=
s
;
});
return
<
IFilterMetadata
>
{
requestUrl
:
details
.
url
,
requestBody
:
details
.
body
,
duration
,
timestamp
,
scoredResults
,
context
:
result
[
'
@odata.context
'
]
};
});
}
private
getRemoteSettingMatcher
(
scoredResults
:
IScoredResults
,
minScore
:
number
,
preferencesModel
:
ISettingsEditorModel
):
ISettingMatcher
{
return
(
setting
:
ISetting
,
group
:
ISettingsGroup
)
=>
{
const
remoteSetting
=
scoredResults
[
getSettingKey
(
setting
.
key
,
group
.
id
)]
||
// extension setting
scoredResults
[
getSettingKey
(
setting
.
key
,
'
core
'
)]
||
// core setting
scoredResults
[
getSettingKey
(
setting
.
key
)];
// core setting from original prod endpoint
if
(
remoteSetting
&&
remoteSetting
.
score
>=
minScore
)
{
const
settingMatches
=
new
SettingMatches
(
this
.
options
.
filter
,
setting
,
false
,
true
,
(
filter
,
setting
)
=>
preferencesModel
.
findValueMatches
(
filter
,
setting
)).
matches
;
return
{
matches
:
settingMatches
,
score
:
remoteSetting
.
score
};
}
return
null
;
};
}
private
async
prepareRequest
(
query
:
string
,
filterPage
=
0
):
Promise
<
IBingRequestDetails
>
{
const
verbatimQuery
=
query
;
query
=
escapeSpecialChars
(
query
);
const
boost
=
10
;
const
boostedQuery
=
`(
${
query
}
)^
${
boost
}
`
;
// Appending Fuzzy after each word.
query
=
query
.
replace
(
/
\
+/g
,
'
~
'
)
+
'
~
'
;
const
encodedQuery
=
encodeURIComponent
(
boostedQuery
+
'
||
'
+
query
);
let
url
=
`
${
this
.
options
.
endpoint
.
urlBase
}
`
;
if
(
this
.
options
.
endpoint
.
key
)
{
url
+=
`
${
API_VERSION
}
&
${
QUERY_TYPE
}
`
;
}
const
extensions
=
await
this
.
installedExtensions
;
const
filters
=
this
.
options
.
newExtensionsOnly
?
[
`diminish eq 'latest'`
]
:
this
.
getVersionFilters
(
extensions
,
this
.
environmentService
.
settingsSearchBuildId
);
const
filterStr
=
filters
.
slice
(
filterPage
*
RemoteSearchProvider
.
MAX_REQUEST_FILTERS
,
(
filterPage
+
1
)
*
RemoteSearchProvider
.
MAX_REQUEST_FILTERS
)
.
join
(
'
or
'
);
const
hasMoreFilters
=
filters
.
length
>
(
filterPage
+
1
)
*
RemoteSearchProvider
.
MAX_REQUEST_FILTERS
;
const
body
=
JSON
.
stringify
({
query
:
encodedQuery
,
filters
:
encodeURIComponent
(
filterStr
),
rawQuery
:
encodeURIComponent
(
verbatimQuery
)
});
return
{
url
,
body
,
hasMoreFilters
};
}
private
getVersionFilters
(
exts
:
ILocalExtension
[],
buildNumber
?:
number
):
string
[]
{
// Only search extensions that contribute settings
const
filters
=
exts
.
filter
(
ext
=>
ext
.
manifest
.
contributes
&&
ext
.
manifest
.
contributes
.
configuration
)
.
map
(
ext
=>
this
.
getExtensionFilter
(
ext
));
if
(
buildNumber
)
{
filters
.
push
(
`(packageid eq 'core' and startbuildno le '
${
buildNumber
}
' and endbuildno ge '
${
buildNumber
}
')`
);
}
return
filters
;
}
private
getExtensionFilter
(
ext
:
ILocalExtension
):
string
{
const
uuid
=
ext
.
identifier
.
uuid
;
const
versionString
=
ext
.
manifest
.
version
.
split
(
'
.
'
)
.
map
(
versionPart
=>
strings
.
pad
(
<
any
>
versionPart
,
10
))
.
join
(
''
);
return
`(packageid eq '
${
uuid
}
' and startbuildno le '
${
versionString
}
' and endbuildno ge '
${
versionString
}
')`
;
}
}
function
getSettingKey
(
name
:
string
,
packageId
?:
string
):
string
{
return
packageId
?
packageId
+
'
##
'
+
name
:
name
;
}
const
API_VERSION
=
'
api-version=2016-09-01-Preview
'
;
const
QUERY_TYPE
=
'
querytype=full
'
;
function
escapeSpecialChars
(
query
:
string
):
string
{
return
query
.
replace
(
/
\.
/g
,
'
'
)
.
replace
(
/
[\\/
+
\-
&|!"~*?:(){}
\[\]\^]
/g
,
'
\\
$&
'
)
.
replace
(
/ /g
,
'
'
)
// collapse spaces
.
trim
();
}
function
remoteSettingToISetting
(
remoteSetting
:
IRemoteSetting
):
IExtensionSetting
{
return
{
description
:
remoteSetting
.
description
.
split
(
'
\n
'
),
descriptionIsMarkdown
:
false
,
descriptionRanges
:
[],
key
:
remoteSetting
.
key
,
keyRange
:
nullRange
,
value
:
remoteSetting
.
defaultValue
,
range
:
nullRange
,
valueRange
:
nullRange
,
overrides
:
[],
extensionName
:
remoteSetting
.
extensionName
,
extensionPublisher
:
remoteSetting
.
extensionPublisher
};
}
\ No newline at end of file
src/vs/workbench/workbench.main.ts
浏览文件 @
a3ec0455
...
...
@@ -200,7 +200,7 @@ import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
import
'
vs/workbench/contrib/preferences/browser/preferences.contribution
'
;
import
'
vs/workbench/contrib/preferences/browser/keybindingsEditorContribution
'
;
import
{
IPreferencesSearchService
}
from
'
vs/workbench/contrib/preferences/common/preferences
'
;
import
{
PreferencesSearchService
}
from
'
vs/workbench/contrib/preferences/
electron-
browser/preferencesSearch
'
;
import
{
PreferencesSearchService
}
from
'
vs/workbench/contrib/preferences/browser/preferencesSearch
'
;
registerSingleton
(
IPreferencesSearchService
,
PreferencesSearchService
,
true
);
// Logs
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录