Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
3dea65ba
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,体验更适合开发者的 AI 搜索 >>
未验证
提交
3dea65ba
编写于
12月 13, 2019
作者:
R
Rob Lourens
提交者:
GitHub
12月 13, 2019
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #86563 from jzyrobert/83252-search-sort
#82352 Implement sorting for search results
上级
fc803264
479d4e6e
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
141 addition
and
21 deletion
+141
-21
src/vs/workbench/contrib/search/browser/search.contribution.ts
...s/workbench/contrib/search/browser/search.contribution.ts
+16
-2
src/vs/workbench/contrib/search/browser/searchView.ts
src/vs/workbench/contrib/search/browser/searchView.ts
+59
-11
src/vs/workbench/contrib/search/common/searchModel.ts
src/vs/workbench/contrib/search/common/searchModel.ts
+36
-3
src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts
...rkbench/contrib/search/test/browser/searchViewlet.test.ts
+20
-5
src/vs/workbench/services/search/common/search.ts
src/vs/workbench/services/search/common/search.ts
+10
-0
未找到文件。
src/vs/workbench/contrib/search/browser/search.contribution.ts
浏览文件 @
3dea65ba
...
...
@@ -51,7 +51,7 @@ import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contri
import
{
FileMatchOrMatch
,
ISearchWorkbenchService
,
RenderableMatch
,
SearchWorkbenchService
,
FileMatch
,
Match
,
FolderMatch
}
from
'
vs/workbench/contrib/search/common/searchModel
'
;
import
{
IEditorService
}
from
'
vs/workbench/services/editor/common/editorService
'
;
import
{
IPanelService
}
from
'
vs/workbench/services/panel/common/panelService
'
;
import
{
ISearchConfiguration
,
ISearchConfigurationProperties
,
PANEL_ID
,
VIEWLET_ID
,
VIEW_ID
,
VIEW_CONTAINER
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
ISearchConfiguration
,
ISearchConfigurationProperties
,
PANEL_ID
,
VIEWLET_ID
,
VIEW_ID
,
VIEW_CONTAINER
,
SearchSortOrder
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
IViewletService
}
from
'
vs/workbench/services/viewlet/browser/viewlet
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
ExplorerViewPaneContainer
}
from
'
vs/workbench/contrib/files/browser/explorerViewlet
'
;
...
...
@@ -827,7 +827,21 @@ configurationRegistry.registerConfiguration({
type
:
'
boolean
'
,
default
:
false
,
description
:
nls
.
localize
(
'
search.enableSearchEditorPreview
'
,
"
Experimental: When enabled, allows opening workspace search results in an editor.
"
)
}
},
'
search.sortOrder
'
:
{
'
type
'
:
'
string
'
,
'
enum
'
:
[
SearchSortOrder
.
Default
,
SearchSortOrder
.
FileNames
,
SearchSortOrder
.
Type
,
SearchSortOrder
.
Modified
,
SearchSortOrder
.
CountDescending
,
SearchSortOrder
.
CountAscending
],
'
default
'
:
SearchSortOrder
.
Default
,
'
enumDescriptions
'
:
[
nls
.
localize
(
'
searchSortOrder.default
'
,
'
Results are sorted by folder and file names, in alphabetical order.
'
),
nls
.
localize
(
'
searchSortOrder.filesOnly
'
,
'
Results are sorted by file names ignoring folder order, in alphabetical order.
'
),
nls
.
localize
(
'
searchSortOrder.type
'
,
'
Results are sorted by file extensions, in alphabetical order.
'
),
nls
.
localize
(
'
searchSortOrder.modified
'
,
'
Results are sorted by file last modified date, in descending order.
'
),
nls
.
localize
(
'
searchSortOrder.countDescending
'
,
'
Results are sorted by count per file, in descending order.
'
),
nls
.
localize
(
'
searchSortOrder.countAscending
'
,
'
Results are sorted by count per file, in ascending order.
'
)
],
'
description
'
:
nls
.
localize
(
'
search.sortOrder
'
,
"
Controls sorting order of search results.
"
)
},
}
});
...
...
src/vs/workbench/contrib/search/browser/searchView.ts
浏览文件 @
3dea65ba
...
...
@@ -34,7 +34,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import
{
TreeResourceNavigator2
,
WorkbenchObjectTree
,
getSelectionKeyboardEvent
}
from
'
vs/platform/list/browser/listService
'
;
import
{
INotificationService
}
from
'
vs/platform/notification/common/notification
'
;
import
{
IProgressService
,
IProgressStep
,
IProgress
}
from
'
vs/platform/progress/common/progress
'
;
import
{
IPatternInfo
,
ISearchComplete
,
ISearchConfiguration
,
ISearchConfigurationProperties
,
ITextQuery
,
VIEW_ID
,
VIEWLET_ID
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
IPatternInfo
,
ISearchComplete
,
ISearchConfiguration
,
ISearchConfigurationProperties
,
ITextQuery
,
VIEW_ID
,
VIEWLET_ID
,
SearchSortOrder
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
ISearchHistoryService
,
ISearchHistoryValues
}
from
'
vs/workbench/contrib/search/common/searchHistoryService
'
;
import
{
diffInserted
,
diffInsertedOutline
,
diffRemoved
,
diffRemovedOutline
,
editorFindMatchHighlight
,
editorFindMatchHighlightBorder
,
listActiveSelectionForeground
,
foreground
}
from
'
vs/platform/theme/common/colorRegistry
'
;
import
{
ICssStyleCollector
,
ITheme
,
IThemeService
,
registerThemingParticipant
}
from
'
vs/platform/theme/common/themeService
'
;
...
...
@@ -197,6 +197,16 @@ export class SearchView extends ViewPane {
this
.
enableSearchEditorPreview
.
set
(
this
.
searchConfig
.
enableSearchEditorPreview
);
}
});
this
.
configurationService
.
onDidChangeConfiguration
(
e
=>
{
if
(
e
.
affectsConfiguration
(
'
search.sortOrder
'
))
{
if
(
this
.
searchConfig
.
sortOrder
===
SearchSortOrder
.
Modified
)
{
// If changing away from modified, remove all fileStats
// so that updated files are re-retrieved next time.
this
.
removeFileStats
();
}
this
.
refreshTree
();
}
});
this
.
viewModel
=
this
.
_register
(
this
.
searchWorkbenchService
.
searchModel
);
this
.
queryBuilder
=
this
.
instantiationService
.
createInstance
(
QueryBuilder
);
...
...
@@ -503,13 +513,25 @@ export class SearchView extends ViewPane {
const
collapseResults
=
this
.
searchConfig
.
collapseResults
;
if
(
!
event
||
event
.
added
||
event
.
removed
)
{
// Refresh whole tree
this
.
tree
.
setChildren
(
null
,
this
.
createResultIterator
(
collapseResults
));
if
(
this
.
searchConfig
.
sortOrder
===
SearchSortOrder
.
Modified
)
{
// Ensure all matches have retrieved their file stat
this
.
retrieveFileStats
()
.
then
(()
=>
this
.
tree
.
setChildren
(
null
,
this
.
createResultIterator
(
collapseResults
)));
}
else
{
this
.
tree
.
setChildren
(
null
,
this
.
createResultIterator
(
collapseResults
));
}
}
else
{
// FileMatch modified, refresh those elements
event
.
elements
.
forEach
(
element
=>
{
this
.
tree
.
setChildren
(
element
,
this
.
createIterator
(
element
,
collapseResults
));
this
.
tree
.
rerender
(
element
);
});
// If updated counts affect our search order, re-sort the view.
if
(
this
.
searchConfig
.
sortOrder
===
SearchSortOrder
.
CountAscending
||
this
.
searchConfig
.
sortOrder
===
SearchSortOrder
.
CountDescending
)
{
this
.
tree
.
setChildren
(
null
,
this
.
createResultIterator
(
collapseResults
));
}
else
{
// FileMatch modified, refresh those elements
event
.
elements
.
forEach
(
element
=>
{
this
.
tree
.
setChildren
(
element
,
this
.
createIterator
(
element
,
collapseResults
));
this
.
tree
.
rerender
(
element
);
});
}
}
}
...
...
@@ -530,9 +552,10 @@ export class SearchView extends ViewPane {
}
private
createFolderIterator
(
folderMatch
:
FolderMatch
,
collapseResults
:
ISearchConfigurationProperties
[
'
collapseResults
'
]):
Iterator
<
ITreeElement
<
RenderableMatch
>>
{
const
sortOrder
=
this
.
searchConfig
.
sortOrder
;
const
filesIt
=
Iterator
.
fromArray
(
folderMatch
.
matches
()
.
sort
(
searchMatchComparer
));
.
sort
(
(
a
,
b
)
=>
searchMatchComparer
(
a
,
b
,
sortOrder
)
));
return
Iterator
.
map
(
filesIt
,
fileMatch
=>
{
const
children
=
this
.
createFileIterator
(
fileMatch
);
...
...
@@ -1703,14 +1726,23 @@ export class SearchView extends ViewPane {
}
private
onFilesChanged
(
e
:
FileChangesEvent
):
void
{
if
(
!
this
.
viewModel
||
!
e
.
gotDeleted
(
))
{
if
(
!
this
.
viewModel
||
(
this
.
searchConfig
.
sortOrder
!==
SearchSortOrder
.
Modified
&&
!
e
.
gotDeleted
()
))
{
return
;
}
const
matches
=
this
.
viewModel
.
searchResult
.
matches
();
if
(
e
.
gotDeleted
())
{
const
deletedMatches
=
matches
.
filter
(
m
=>
e
.
contains
(
m
.
resource
,
FileChangeType
.
DELETED
));
const
changedMatches
=
matches
.
filter
(
m
=>
e
.
contains
(
m
.
resource
,
FileChangeType
.
DELETED
));
this
.
viewModel
.
searchResult
.
remove
(
changedMatches
);
this
.
viewModel
.
searchResult
.
remove
(
deletedMatches
);
}
else
{
// Check if the changed file contained matches
const
changedMatches
=
matches
.
filter
(
m
=>
e
.
contains
(
m
.
resource
));
if
(
changedMatches
.
length
&&
this
.
searchConfig
.
sortOrder
===
SearchSortOrder
.
Modified
)
{
// No matches need to be removed, but modified files need to have their file stat updated.
this
.
updateFileStats
(
changedMatches
).
then
(()
=>
this
.
refreshTree
());
}
}
}
getActions
():
IAction
[]
{
...
...
@@ -1783,6 +1815,22 @@ export class SearchView extends ViewPane {
super
.
saveState
();
}
private
async
retrieveFileStats
():
Promise
<
void
>
{
const
files
=
this
.
searchResult
.
matches
().
filter
(
f
=>
!
f
.
fileStat
).
map
(
f
=>
f
.
resolveFileStat
(
this
.
fileService
));
await
Promise
.
all
(
files
);
}
private
async
updateFileStats
(
elements
:
FileMatch
[]):
Promise
<
void
>
{
const
files
=
elements
.
map
(
f
=>
f
.
resolveFileStat
(
this
.
fileService
));
await
Promise
.
all
(
files
);
}
private
removeFileStats
():
void
{
for
(
const
fileMatch
of
this
.
searchResult
.
matches
())
{
fileMatch
.
fileStat
=
undefined
;
}
}
dispose
():
void
{
this
.
isDisposed
=
true
;
this
.
saveState
();
...
...
src/vs/workbench/contrib/search/common/searchModel.ts
浏览文件 @
3dea65ba
...
...
@@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import
{
createDecorator
,
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
IProgress
,
IProgressStep
}
from
'
vs/platform/progress/common/progress
'
;
import
{
ReplacePattern
}
from
'
vs/workbench/services/search/common/replace
'
;
import
{
IFileMatch
,
IPatternInfo
,
ISearchComplete
,
ISearchProgressItem
,
ISearchConfigurationProperties
,
ISearchService
,
ITextQuery
,
ITextSearchPreviewOptions
,
ITextSearchMatch
,
ITextSearchStats
,
resultIsMatch
,
ISearchRange
,
OneLineRange
,
ITextSearchContext
,
ITextSearchResult
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
IFileMatch
,
IPatternInfo
,
ISearchComplete
,
ISearchProgressItem
,
ISearchConfigurationProperties
,
ISearchService
,
ITextQuery
,
ITextSearchPreviewOptions
,
ITextSearchMatch
,
ITextSearchStats
,
resultIsMatch
,
ISearchRange
,
OneLineRange
,
ITextSearchContext
,
ITextSearchResult
,
SearchSortOrder
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
ITelemetryService
}
from
'
vs/platform/telemetry/common/telemetry
'
;
import
{
overviewRulerFindMatchForeground
,
minimapFindMatch
}
from
'
vs/platform/theme/common/colorRegistry
'
;
import
{
themeColorFromId
}
from
'
vs/platform/theme/common/themeService
'
;
...
...
@@ -29,6 +29,8 @@ import { editorMatchesToTextSearchResults, addContextToEditorMatches } from 'vs/
import
{
withNullAsUndefined
}
from
'
vs/base/common/types
'
;
import
{
memoize
}
from
'
vs/base/common/decorators
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
compareFileNames
,
compareFileExtensions
,
comparePaths
}
from
'
vs/base/common/comparers
'
;
import
{
IFileService
,
IFileStatWithMetadata
}
from
'
vs/platform/files/common/files
'
;
export
class
Match
{
...
...
@@ -188,6 +190,7 @@ export class FileMatch extends Disposable implements IFileMatch {
readonly
onDispose
:
Event
<
void
>
=
this
.
_onDispose
.
event
;
private
_resource
:
URI
;
private
_fileStat
?:
IFileStatWithMetadata
;
private
_model
:
ITextModel
|
null
=
null
;
private
_modelListener
:
IDisposable
|
null
=
null
;
private
_matches
:
Map
<
string
,
Match
>
;
...
...
@@ -411,6 +414,18 @@ export class FileMatch extends Disposable implements IFileMatch {
}
}
async
resolveFileStat
(
fileService
:
IFileService
):
Promise
<
void
>
{
this
.
_fileStat
=
await
fileService
.
resolve
(
this
.
resource
,
{
resolveMetadata
:
true
});
}
public
get
fileStat
():
IFileStatWithMetadata
|
undefined
{
return
this
.
_fileStat
;
}
public
set
fileStat
(
stat
:
IFileStatWithMetadata
|
undefined
)
{
this
.
_fileStat
=
stat
;
}
dispose
():
void
{
this
.
setSelectedMatch
(
null
);
this
.
unbindModel
();
...
...
@@ -633,13 +648,31 @@ export class FolderMatchWithResource extends FolderMatch {
* Compares instances of the same match type. Different match types should not be siblings
* and their sort order is undefined.
*/
export
function
searchMatchComparer
(
elementA
:
RenderableMatch
,
elementB
:
RenderableMatch
):
number
{
export
function
searchMatchComparer
(
elementA
:
RenderableMatch
,
elementB
:
RenderableMatch
,
sortOrder
:
SearchSortOrder
=
SearchSortOrder
.
Default
):
number
{
if
(
elementA
instanceof
FolderMatch
&&
elementB
instanceof
FolderMatch
)
{
return
elementA
.
index
()
-
elementB
.
index
();
}
if
(
elementA
instanceof
FileMatch
&&
elementB
instanceof
FileMatch
)
{
return
elementA
.
resource
.
fsPath
.
localeCompare
(
elementB
.
resource
.
fsPath
)
||
elementA
.
name
().
localeCompare
(
elementB
.
name
());
switch
(
sortOrder
)
{
case
SearchSortOrder
.
CountDescending
:
return
elementB
.
count
()
-
elementA
.
count
();
case
SearchSortOrder
.
CountAscending
:
return
elementA
.
count
()
-
elementB
.
count
();
case
SearchSortOrder
.
Type
:
return
compareFileExtensions
(
elementA
.
name
(),
elementB
.
name
());
case
SearchSortOrder
.
FileNames
:
return
compareFileNames
(
elementA
.
name
(),
elementB
.
name
());
case
SearchSortOrder
.
Modified
:
const
fileStatA
=
elementA
.
fileStat
;
const
fileStatB
=
elementB
.
fileStat
;
if
(
fileStatA
&&
fileStatB
)
{
return
fileStatB
.
mtime
-
fileStatA
.
mtime
;
}
// Fall through otherwise
default
:
return
comparePaths
(
elementA
.
resource
.
fsPath
,
elementB
.
resource
.
fsPath
)
||
compareFileNames
(
elementA
.
name
(),
elementB
.
name
());
}
}
if
(
elementA
instanceof
Match
&&
elementB
instanceof
Match
)
{
...
...
src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts
浏览文件 @
3dea65ba
...
...
@@ -9,11 +9,12 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
TestConfigurationService
}
from
'
vs/platform/configuration/test/common/testConfigurationService
'
;
import
{
TestInstantiationService
}
from
'
vs/platform/instantiation/test/common/instantiationServiceMock
'
;
import
{
IFileMatch
,
ITextSearchMatch
,
OneLineRange
,
QueryType
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
IFileMatch
,
ITextSearchMatch
,
OneLineRange
,
QueryType
,
SearchSortOrder
}
from
'
vs/workbench/services/search/common/search
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
TestWorkspace
}
from
'
vs/platform/workspace/test/common/testWorkspace
'
;
import
{
FileMatch
,
Match
,
searchMatchComparer
,
SearchResult
}
from
'
vs/workbench/contrib/search/common/searchModel
'
;
import
{
TestContextService
}
from
'
vs/workbench/test/workbenchTestServices
'
;
import
{
isWindows
}
from
'
vs/base/common/platform
'
;
suite
(
'
Search - Viewlet
'
,
()
=>
{
let
instantiation
:
TestInstantiationService
;
...
...
@@ -63,9 +64,9 @@ suite('Search - Viewlet', () => {
});
test
(
'
Comparer
'
,
()
=>
{
let
fileMatch1
=
aFileMatch
(
'
C:
\\
foo
'
);
let
fileMatch2
=
aFileMatch
(
'
C:
\\
with
\\
path
'
);
let
fileMatch3
=
aFileMatch
(
'
C:
\\
with
\\
path
\\
foo
'
);
let
fileMatch1
=
aFileMatch
(
isWindows
?
'
C:
\\
foo
'
:
'
/c/
foo
'
);
let
fileMatch2
=
aFileMatch
(
isWindows
?
'
C:
\\
with
\\
path
'
:
'
/c/with/
path
'
);
let
fileMatch3
=
aFileMatch
(
isWindows
?
'
C:
\\
with
\\
path
\\
foo
'
:
'
/c/with/path/
foo
'
);
let
lineMatch1
=
new
Match
(
fileMatch1
,
[
'
bar
'
],
new
OneLineRange
(
0
,
1
,
1
),
new
OneLineRange
(
0
,
1
,
1
));
let
lineMatch2
=
new
Match
(
fileMatch1
,
[
'
bar
'
],
new
OneLineRange
(
0
,
1
,
1
),
new
OneLineRange
(
2
,
1
,
1
));
let
lineMatch3
=
new
Match
(
fileMatch1
,
[
'
bar
'
],
new
OneLineRange
(
0
,
1
,
1
),
new
OneLineRange
(
2
,
1
,
1
));
...
...
@@ -80,9 +81,23 @@ suite('Search - Viewlet', () => {
assert
(
searchMatchComparer
(
lineMatch2
,
lineMatch3
)
===
0
);
});
test
(
'
Advanced Comparer
'
,
()
=>
{
let
fileMatch1
=
aFileMatch
(
isWindows
?
'
C:
\\
with
\\
path
\\
foo10
'
:
'
/c/with/path/foo10
'
);
let
fileMatch2
=
aFileMatch
(
isWindows
?
'
C:
\\
with
\\
path2
\\
foo1
'
:
'
/c/with/path2/foo1
'
);
let
fileMatch3
=
aFileMatch
(
isWindows
?
'
C:
\\
with
\\
path2
\\
bar.a
'
:
'
/c/with/path2/bar.a
'
);
let
fileMatch4
=
aFileMatch
(
isWindows
?
'
C:
\\
with
\\
path2
\\
bar.b
'
:
'
/c/with/path2/bar.b
'
);
// By default, path < path2
assert
(
searchMatchComparer
(
fileMatch1
,
fileMatch2
)
<
0
);
// By filenames, foo10 > foo1
assert
(
searchMatchComparer
(
fileMatch1
,
fileMatch2
,
SearchSortOrder
.
FileNames
)
>
0
);
// By type, bar.a < bar.b
assert
(
searchMatchComparer
(
fileMatch3
,
fileMatch4
,
SearchSortOrder
.
Type
)
<
0
);
});
function
aFileMatch
(
path
:
string
,
searchResult
?:
SearchResult
,
...
lineMatches
:
ITextSearchMatch
[]):
FileMatch
{
let
rawMatch
:
IFileMatch
=
{
resource
:
uri
.
file
(
'
C:
\\
'
+
path
),
resource
:
uri
.
file
(
path
),
results
:
lineMatches
};
return
instantiation
.
createInstance
(
FileMatch
,
null
,
null
,
null
,
searchResult
,
rawMatch
);
...
...
src/vs/workbench/services/search/common/search.ts
浏览文件 @
3dea65ba
...
...
@@ -311,6 +311,15 @@ export class OneLineRange extends SearchRange {
}
}
export
const
enum
SearchSortOrder
{
Default
=
'
default
'
,
FileNames
=
'
fileNames
'
,
Type
=
'
type
'
,
Modified
=
'
modified
'
,
CountDescending
=
'
countDescending
'
,
CountAscending
=
'
countAscending
'
}
export
interface
ISearchConfigurationProperties
{
exclude
:
glob
.
IExpression
;
useRipgrep
:
boolean
;
...
...
@@ -333,6 +342,7 @@ export interface ISearchConfigurationProperties {
searchOnTypeDebouncePeriod
:
number
;
enableSearchEditorPreview
:
boolean
;
searchEditorPreviewForceAbsolutePaths
:
boolean
;
sortOrder
:
SearchSortOrder
;
}
export
interface
ISearchConfiguration
extends
IFilesConfiguration
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录