Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
掘金者说
vscode
提交
d616c781
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,发现更多精彩内容 >>
提交
d616c781
编写于
10月 15, 2018
作者:
R
Rob Lourens
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Refactor search - remove search-rg text impl
上级
dc2206a1
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
32 addition
and
385 deletion
+32
-385
extensions/search-rg/src/extension.ts
extensions/search-rg/src/extension.ts
+1
-8
extensions/search-rg/src/ripgrepFileSearch.ts
extensions/search-rg/src/ripgrepFileSearch.ts
+31
-1
extensions/search-rg/src/ripgrepTextSearch.ts
extensions/search-rg/src/ripgrepTextSearch.ts
+0
-376
未找到文件。
extensions/search-rg/src/extension.ts
浏览文件 @
d616c781
...
...
@@ -5,7 +5,6 @@
import
*
as
vscode
from
'
vscode
'
;
import
{
RipgrepFileSearchEngine
}
from
'
./ripgrepFileSearch
'
;
import
{
RipgrepTextSearchEngine
}
from
'
./ripgrepTextSearch
'
;
export
function
activate
():
void
{
if
(
vscode
.
workspace
.
getConfiguration
(
'
searchRipgrep
'
).
get
(
'
enable
'
))
{
...
...
@@ -13,22 +12,16 @@ export function activate(): void {
const
provider
=
new
RipgrepSearchProvider
(
outputChannel
);
vscode
.
workspace
.
registerFileIndexProvider
(
'
file
'
,
provider
);
vscode
.
workspace
.
registerTextSearchProvider
(
'
file
'
,
provider
);
}
}
class
RipgrepSearchProvider
implements
vscode
.
FileIndexProvider
,
vscode
.
TextSearchProvider
{
class
RipgrepSearchProvider
implements
vscode
.
FileIndexProvider
{
private
inProgress
:
Set
<
vscode
.
CancellationTokenSource
>
=
new
Set
();
constructor
(
private
outputChannel
:
vscode
.
OutputChannel
)
{
process
.
once
(
'
exit
'
,
()
=>
this
.
dispose
());
}
provideTextSearchResults
(
query
:
vscode
.
TextSearchQuery
,
options
:
vscode
.
TextSearchOptions
,
progress
:
vscode
.
Progress
<
vscode
.
TextSearchResult
>
,
token
:
vscode
.
CancellationToken
):
Promise
<
vscode
.
TextSearchComplete
>
{
const
engine
=
new
RipgrepTextSearchEngine
(
this
.
outputChannel
);
return
this
.
withToken
(
token
,
token
=>
engine
.
provideTextSearchResults
(
query
,
options
,
progress
,
token
));
}
provideFileIndex
(
options
:
vscode
.
FileSearchOptions
,
token
:
vscode
.
CancellationToken
):
Thenable
<
vscode
.
Uri
[]
>
{
const
engine
=
new
RipgrepFileSearchEngine
(
this
.
outputChannel
);
...
...
extensions/search-rg/src/ripgrepFileSearch.ts
浏览文件 @
d616c781
...
...
@@ -9,7 +9,6 @@ import { NodeStringDecoder, StringDecoder } from 'string_decoder';
import
*
as
vscode
from
'
vscode
'
;
import
{
normalizeNFC
,
normalizeNFD
}
from
'
./normalization
'
;
import
{
rgPath
}
from
'
./ripgrep
'
;
import
{
rgErrorMsgForDisplay
}
from
'
./ripgrepTextSearch
'
;
import
{
anchorGlob
,
Maybe
}
from
'
./utils
'
;
const
isMac
=
process
.
platform
===
'
darwin
'
;
...
...
@@ -210,3 +209,34 @@ function getRgArgs(options: vscode.FileSearchOptions): string[] {
return
args
;
}
/**
* Read the first line of stderr and return an error for display or undefined, based on a whitelist.
* Ripgrep produces stderr output which is not from a fatal error, and we only want the search to be
* "failed" when a fatal error was produced.
*/
export
function
rgErrorMsgForDisplay
(
msg
:
string
):
Maybe
<
string
>
{
const
firstLine
=
msg
.
split
(
'
\n
'
)[
0
].
trim
();
if
(
firstLine
.
startsWith
(
'
Error parsing regex
'
))
{
return
firstLine
;
}
if
(
firstLine
.
startsWith
(
'
error parsing glob
'
)
||
firstLine
.
startsWith
(
'
unsupported encoding
'
))
{
// Uppercase first letter
return
firstLine
.
charAt
(
0
).
toUpperCase
()
+
firstLine
.
substr
(
1
);
}
if
(
firstLine
===
`Literal '\\n' not allowed.`
)
{
// I won't localize this because none of the Ripgrep error messages are localized
return
`Literal '\\n' currently not supported`
;
}
if
(
firstLine
.
startsWith
(
'
Literal
'
))
{
// Other unsupported chars
return
firstLine
;
}
return
undefined
;
}
extensions/search-rg/src/ripgrepTextSearch.ts
已删除
100644 → 0
浏览文件 @
dc2206a1
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
*
as
cp
from
'
child_process
'
;
import
{
EventEmitter
}
from
'
events
'
;
import
*
as
path
from
'
path
'
;
import
{
NodeStringDecoder
,
StringDecoder
}
from
'
string_decoder
'
;
import
*
as
vscode
from
'
vscode
'
;
import
{
rgPath
}
from
'
./ripgrep
'
;
import
{
anchorGlob
,
createTextSearchResult
,
Maybe
}
from
'
./utils
'
;
// If vscode-ripgrep is in an .asar file, then the binary is unpacked.
const
rgDiskPath
=
rgPath
.
replace
(
/
\b
node_modules
\.
asar
\b
/
,
'
node_modules.asar.unpacked
'
);
export
class
RipgrepTextSearchEngine
{
constructor
(
private
outputChannel
:
vscode
.
OutputChannel
)
{
}
provideTextSearchResults
(
query
:
vscode
.
TextSearchQuery
,
options
:
vscode
.
TextSearchOptions
,
progress
:
vscode
.
Progress
<
vscode
.
TextSearchResult
>
,
token
:
vscode
.
CancellationToken
):
Thenable
<
vscode
.
TextSearchComplete
>
{
this
.
outputChannel
.
appendLine
(
`provideTextSearchResults
${
query
.
pattern
}
,
${
JSON
.
stringify
({
...
options
,
...{
folder
:
options
.
folder
.
toString
()
}
})}
`
);
return
new
Promise
((
resolve
,
reject
)
=>
{
token
.
onCancellationRequested
(()
=>
cancel
());
const
rgArgs
=
getRgArgs
(
query
,
options
);
const
cwd
=
options
.
folder
.
fsPath
;
const
escapedArgs
=
rgArgs
.
map
(
arg
=>
arg
.
match
(
/^-/
)
?
arg
:
`'
${
arg
}
'`
)
.
join
(
'
'
);
this
.
outputChannel
.
appendLine
(
`rg
${
escapedArgs
}
\n - cwd:
${
cwd
}
`
);
let
rgProc
:
Maybe
<
cp
.
ChildProcess
>
=
cp
.
spawn
(
rgDiskPath
,
rgArgs
,
{
cwd
});
rgProc
.
on
(
'
error
'
,
e
=>
{
console
.
error
(
e
);
this
.
outputChannel
.
append
(
'
Error:
'
+
(
e
&&
e
.
message
));
reject
(
e
);
});
let
gotResult
=
false
;
const
ripgrepParser
=
new
RipgrepParser
(
options
.
maxResults
,
cwd
,
options
.
previewOptions
);
ripgrepParser
.
on
(
'
result
'
,
(
match
:
vscode
.
TextSearchResult
)
=>
{
gotResult
=
true
;
progress
.
report
(
match
);
});
let
isDone
=
false
;
const
cancel
=
()
=>
{
isDone
=
true
;
if
(
rgProc
)
{
rgProc
.
kill
();
}
if
(
ripgrepParser
)
{
ripgrepParser
.
cancel
();
}
};
let
limitHit
=
false
;
ripgrepParser
.
on
(
'
hitLimit
'
,
()
=>
{
limitHit
=
true
;
cancel
();
});
rgProc
.
stdout
.
on
(
'
data
'
,
data
=>
{
ripgrepParser
.
handleData
(
data
);
});
let
gotData
=
false
;
rgProc
.
stdout
.
once
(
'
data
'
,
()
=>
gotData
=
true
);
let
stderr
=
''
;
rgProc
.
stderr
.
on
(
'
data
'
,
data
=>
{
const
message
=
data
.
toString
();
this
.
outputChannel
.
append
(
message
);
stderr
+=
message
;
});
rgProc
.
on
(
'
close
'
,
()
=>
{
this
.
outputChannel
.
appendLine
(
gotData
?
'
Got data from stdout
'
:
'
No data from stdout
'
);
this
.
outputChannel
.
appendLine
(
gotResult
?
'
Got result from parser
'
:
'
No result from parser
'
);
this
.
outputChannel
.
appendLine
(
''
);
if
(
isDone
)
{
resolve
({
limitHit
});
}
else
{
// Trigger last result
ripgrepParser
.
flush
();
rgProc
=
null
;
let
displayMsg
:
Maybe
<
string
>
;
if
(
stderr
&&
!
gotData
&&
(
displayMsg
=
rgErrorMsgForDisplay
(
stderr
)))
{
reject
(
new
Error
(
displayMsg
));
}
else
{
resolve
({
limitHit
});
}
}
});
});
}
}
/**
* Read the first line of stderr and return an error for display or undefined, based on a whitelist.
* Ripgrep produces stderr output which is not from a fatal error, and we only want the search to be
* "failed" when a fatal error was produced.
*/
export
function
rgErrorMsgForDisplay
(
msg
:
string
):
Maybe
<
string
>
{
const
firstLine
=
msg
.
split
(
'
\n
'
)[
0
].
trim
();
if
(
firstLine
.
startsWith
(
'
Error parsing regex
'
))
{
return
firstLine
;
}
if
(
firstLine
.
startsWith
(
'
error parsing glob
'
)
||
firstLine
.
startsWith
(
'
unsupported encoding
'
))
{
// Uppercase first letter
return
firstLine
.
charAt
(
0
).
toUpperCase
()
+
firstLine
.
substr
(
1
);
}
if
(
firstLine
===
`Literal '\\n' not allowed.`
)
{
// I won't localize this because none of the Ripgrep error messages are localized
return
`Literal '\\n' currently not supported`
;
}
if
(
firstLine
.
startsWith
(
'
Literal
'
))
{
// Other unsupported chars
return
firstLine
;
}
return
undefined
;
}
export
class
RipgrepParser
extends
EventEmitter
{
private
remainder
=
''
;
private
isDone
=
false
;
private
stringDecoder
:
NodeStringDecoder
;
private
numResults
=
0
;
constructor
(
private
maxResults
:
number
,
private
rootFolder
:
string
,
private
previewOptions
?:
vscode
.
TextSearchPreviewOptions
)
{
super
();
this
.
stringDecoder
=
new
StringDecoder
();
}
public
cancel
():
void
{
this
.
isDone
=
true
;
}
public
flush
():
void
{
this
.
handleDecodedData
(
this
.
stringDecoder
.
end
());
}
public
handleData
(
data
:
Buffer
|
string
):
void
{
const
dataStr
=
typeof
data
===
'
string
'
?
data
:
this
.
stringDecoder
.
write
(
data
);
this
.
handleDecodedData
(
dataStr
);
}
private
handleDecodedData
(
decodedData
:
string
):
void
{
// If the previous data chunk didn't end in a newline, prepend it to this chunk
const
dataStr
=
this
.
remainder
?
this
.
remainder
+
decodedData
:
decodedData
;
const
dataLines
:
string
[]
=
dataStr
.
split
(
/
\r\n
|
\n
/
);
this
.
remainder
=
dataLines
[
dataLines
.
length
-
1
]
?
<
string
>
dataLines
.
pop
()
:
''
;
for
(
let
l
=
0
;
l
<
dataLines
.
length
;
l
++
)
{
const
line
=
dataLines
[
l
];
if
(
line
)
{
// Empty line at the end of each chunk
this
.
handleLine
(
line
);
}
}
}
private
handleLine
(
outputLine
:
string
):
void
{
if
(
this
.
isDone
)
{
return
;
}
let
parsedLine
:
any
;
try
{
parsedLine
=
JSON
.
parse
(
outputLine
);
}
catch
(
e
)
{
throw
new
Error
(
`malformed line from rg:
${
outputLine
}
`
);
}
if
(
parsedLine
.
type
===
'
match
'
)
{
let
hitLimit
=
false
;
const
uri
=
vscode
.
Uri
.
file
(
path
.
join
(
this
.
rootFolder
,
parsedLine
.
data
.
path
.
text
));
parsedLine
.
data
.
submatches
.
map
((
match
:
any
)
=>
{
if
(
hitLimit
)
{
return
null
;
}
if
(
this
.
numResults
>=
this
.
maxResults
)
{
// Finish the line, then report the result below
hitLimit
=
true
;
}
return
this
.
submatchToResult
(
parsedLine
,
match
,
uri
);
}).
forEach
((
result
:
any
)
=>
{
if
(
result
)
{
this
.
onResult
(
result
);
}
});
if
(
hitLimit
)
{
this
.
cancel
();
this
.
emit
(
'
hitLimit
'
);
}
}
}
private
submatchToResult
(
parsedLine
:
any
,
match
:
any
,
uri
:
vscode
.
Uri
):
vscode
.
TextSearchResult
{
const
lineNumber
=
parsedLine
.
data
.
line_number
-
1
;
let
matchText
=
parsedLine
.
data
.
lines
.
bytes
?
new
Buffer
(
parsedLine
.
data
.
lines
.
bytes
,
'
base64
'
).
toString
()
:
parsedLine
.
data
.
lines
.
text
;
let
start
=
match
.
start
;
let
end
=
match
.
end
;
if
(
lineNumber
===
0
)
{
if
(
startsWithUTF8BOM
(
matchText
))
{
matchText
=
stripUTF8BOM
(
matchText
);
start
-=
3
;
end
-=
3
;
}
}
const
range
=
new
vscode
.
Range
(
lineNumber
,
start
,
lineNumber
,
end
);
return
createTextSearchResult
(
uri
,
matchText
,
range
,
this
.
previewOptions
);
}
private
onResult
(
match
:
vscode
.
TextSearchResult
):
void
{
this
.
emit
(
'
result
'
,
match
);
}
}
function
getRgArgs
(
query
:
vscode
.
TextSearchQuery
,
options
:
vscode
.
TextSearchOptions
):
string
[]
{
const
args
=
[
'
--hidden
'
,
'
--heading
'
,
'
--line-number
'
,
'
--color
'
,
'
ansi
'
,
'
--colors
'
,
'
path:none
'
,
'
--colors
'
,
'
line:none
'
,
'
--colors
'
,
'
match:fg:red
'
,
'
--colors
'
,
'
match:style:nobold
'
];
args
.
push
(
query
.
isCaseSensitive
?
'
--case-sensitive
'
:
'
--ignore-case
'
);
options
.
includes
.
map
(
anchorGlob
)
.
forEach
(
globArg
=>
args
.
push
(
'
-g
'
,
globArg
));
options
.
excludes
.
map
(
anchorGlob
)
.
forEach
(
rgGlob
=>
args
.
push
(
'
-g
'
,
`!
${
rgGlob
}
`
));
if
(
options
.
maxFileSize
)
{
args
.
push
(
'
--max-filesize
'
,
options
.
maxFileSize
+
''
);
}
if
(
options
.
useIgnoreFiles
)
{
args
.
push
(
'
--no-ignore-parent
'
);
}
else
{
// Don't use .gitignore or .ignore
args
.
push
(
'
--no-ignore
'
);
}
if
(
options
.
followSymlinks
)
{
args
.
push
(
'
--follow
'
);
}
if
(
options
.
encoding
)
{
args
.
push
(
'
--encoding
'
,
options
.
encoding
);
}
// Ripgrep handles -- as a -- arg separator. Only --.
// - is ok, --- is ok, --some-flag is handled as query text. Need to special case.
if
(
query
.
pattern
===
'
--
'
)
{
query
.
isRegExp
=
true
;
query
.
pattern
=
'
\\
-
\\
-
'
;
}
let
searchPatternAfterDoubleDashes
:
Maybe
<
string
>
;
if
(
query
.
isWordMatch
)
{
const
regexp
=
createRegExp
(
query
.
pattern
,
!!
query
.
isRegExp
,
{
wholeWord
:
query
.
isWordMatch
});
const
regexpStr
=
regexp
.
source
.
replace
(
/
\\\/
/g
,
'
/
'
);
// RegExp.source arbitrarily returns escaped slashes. Search and destroy.
args
.
push
(
'
--regexp
'
,
regexpStr
);
}
else
if
(
query
.
isRegExp
)
{
args
.
push
(
'
--regexp
'
,
fixRegexEndingPattern
(
query
.
pattern
));
}
else
{
searchPatternAfterDoubleDashes
=
query
.
pattern
;
args
.
push
(
'
--fixed-strings
'
);
}
args
.
push
(
'
--no-config
'
);
if
(
!
options
.
useGlobalIgnoreFiles
)
{
args
.
push
(
'
--no-ignore-global
'
);
}
args
.
push
(
'
--json
'
);
// Folder to search
args
.
push
(
'
--
'
);
if
(
searchPatternAfterDoubleDashes
)
{
// Put the query after --, in case the query starts with a dash
args
.
push
(
searchPatternAfterDoubleDashes
);
}
args
.
push
(
'
.
'
);
return
args
;
}
interface
RegExpOptions
{
matchCase
?:
boolean
;
wholeWord
?:
boolean
;
multiline
?:
boolean
;
global
?:
boolean
;
}
function
createRegExp
(
searchString
:
string
,
isRegex
:
boolean
,
options
:
RegExpOptions
=
{}):
RegExp
{
if
(
!
searchString
)
{
throw
new
Error
(
'
Cannot create regex from empty string
'
);
}
if
(
!
isRegex
)
{
searchString
=
escapeRegExpCharacters
(
searchString
);
}
if
(
options
.
wholeWord
)
{
if
(
!
/
\B
/
.
test
(
searchString
.
charAt
(
0
)))
{
searchString
=
'
\\
b
'
+
searchString
;
}
if
(
!
/
\B
/
.
test
(
searchString
.
charAt
(
searchString
.
length
-
1
)))
{
searchString
=
searchString
+
'
\\
b
'
;
}
}
let
modifiers
=
''
;
if
(
options
.
global
)
{
modifiers
+=
'
g
'
;
}
if
(
!
options
.
matchCase
)
{
modifiers
+=
'
i
'
;
}
if
(
options
.
multiline
)
{
modifiers
+=
'
m
'
;
}
return
new
RegExp
(
searchString
,
modifiers
);
}
/**
* Escapes regular expression characters in a given string
*/
function
escapeRegExpCharacters
(
value
:
string
):
string
{
return
value
.
replace
(
/
[\-\\\{\}\*\+\?\|\^\$\.\[\]\(\)\#]
/g
,
'
\\
$&
'
);
}
// -- UTF-8 BOM
const
UTF8_BOM
=
65279
;
function
startsWithUTF8BOM
(
str
:
string
):
boolean
{
return
!!
(
str
&&
str
.
length
>
0
&&
str
.
charCodeAt
(
0
)
===
UTF8_BOM
);
}
function
stripUTF8BOM
(
str
:
string
):
string
{
return
startsWithUTF8BOM
(
str
)
?
str
.
substr
(
1
)
:
str
;
}
function
fixRegexEndingPattern
(
pattern
:
string
):
string
{
// Replace an unescaped $ at the end of the pattern with \r?$
// Match $ preceeded by none or even number of literal \
return
pattern
.
match
(
/
([^\\]
|^
)(\\\\)
*
\$
$/
)
?
pattern
.
replace
(
/
\$
$/
,
'
\\
r?$
'
)
:
pattern
;
}
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录