Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
徽霖
Vscode
提交
5b48bc57
V
Vscode
项目概览
徽霖
/
Vscode
通知
9
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,发现更多精彩内容 >>
未验证
提交
5b48bc57
编写于
3月 22, 2018
作者:
M
Martin Aeschlimann
提交者:
GitHub
3月 22, 2018
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #46296 from Microsoft/octref/htmlPathRefactor
HTML path refactor
上级
d6bdff7f
d5b99caa
变更
9
隐藏空白更改
内联
并排
Showing
9 changed file
with
292 addition
and
361 deletion
+292
-361
extensions/html-language-features/.vscode/launch.json
extensions/html-language-features/.vscode/launch.json
+2
-1
extensions/html-language-features/server/src/modes/pathCompletion.ts
...html-language-features/server/src/modes/pathCompletion.ts
+71
-43
extensions/html-language-features/server/src/test/completions.test.ts
...tml-language-features/server/src/test/completions.test.ts
+219
-80
extensions/html-language-features/server/src/test/pathCompletion/pathCompletion.test.ts
...res/server/src/test/pathCompletion/pathCompletion.test.ts
+0
-237
extensions/html-language-features/server/src/test/pathCompletionFixtures/about/about.css
...es/server/src/test/pathCompletionFixtures/about/about.css
+0
-0
extensions/html-language-features/server/src/test/pathCompletionFixtures/about/about.html
...s/server/src/test/pathCompletionFixtures/about/about.html
+0
-0
extensions/html-language-features/server/src/test/pathCompletionFixtures/index.html
...eatures/server/src/test/pathCompletionFixtures/index.html
+0
-0
extensions/html-language-features/server/src/test/pathCompletionFixtures/src/feature.js
...res/server/src/test/pathCompletionFixtures/src/feature.js
+0
-0
extensions/html-language-features/server/src/test/pathCompletionFixtures/src/test.js
...atures/server/src/test/pathCompletionFixtures/src/test.js
+0
-0
未找到文件。
extensions/html-language-features/.vscode/launch.json
浏览文件 @
5b48bc57
...
...
@@ -38,7 +38,8 @@
"port"
:
6045
,
"protocol"
:
"inspector"
,
"sourceMaps"
:
true
,
"outFiles"
:
[
"${workspaceFolder}/server/out/**/*.js"
]
"outFiles"
:
[
"${workspaceFolder}/server/out/**/*.js"
],
"restart"
:
true
}
]
}
\ No newline at end of file
extensions/html-language-features/server/src/modes/pathCompletion.ts
浏览文件 @
5b48bc57
...
...
@@ -19,21 +19,32 @@ export function getPathCompletionParticipant(
result
:
CompletionList
):
ICompletionParticipant
{
return
{
onHtmlAttributeValue
:
({
tag
,
attribute
,
value
,
range
})
=>
{
onHtmlAttributeValue
:
({
tag
,
position
,
attribute
,
value
:
valueBeforeCursor
,
range
})
=>
{
const
fullValue
=
getFullValueWithoutQuotes
(
document
,
range
);
if
(
shouldDoPathCompletion
(
tag
,
attribute
,
v
alue
))
{
if
(
shouldDoPathCompletion
(
tag
,
attribute
,
fullV
alue
))
{
if
(
!
workspaceFolders
||
workspaceFolders
.
length
===
0
)
{
return
;
}
const
workspaceRoot
=
resolveWorkspaceRoot
(
document
,
workspaceFolders
);
const
suggestions
=
providePathSuggestions
(
value
,
range
,
URI
.
parse
(
document
.
uri
).
fsPath
,
workspaceRoot
);
const
paths
=
providePaths
(
valueBeforeCursor
,
URI
.
parse
(
document
.
uri
).
fsPath
,
workspaceRoot
);
const
suggestions
=
paths
.
map
(
p
=>
pathToSuggestion
(
p
,
valueBeforeCursor
,
fullValue
,
range
));
result
.
items
=
[...
suggestions
,
...
result
.
items
];
}
}
};
}
function
getFullValueWithoutQuotes
(
document
:
TextDocument
,
range
:
Range
)
{
const
fullValue
=
document
.
getText
(
range
);
if
(
startsWith
(
fullValue
,
`'`
)
||
startsWith
(
fullValue
,
`"`
))
{
return
fullValue
.
slice
(
1
,
-
1
);
}
else
{
return
fullValue
;
}
}
function
shouldDoPathCompletion
(
tag
:
string
,
attr
:
string
,
value
:
string
):
boolean
{
if
(
startsWith
(
value
,
'
http
'
)
||
startsWith
(
value
,
'
https
'
)
||
startsWith
(
value
,
'
//
'
))
{
return
false
;
...
...
@@ -50,59 +61,78 @@ function shouldDoPathCompletion(tag: string, attr: string, value: string): boole
return
false
;
}
export
function
providePathSuggestions
(
value
:
string
,
range
:
Range
,
activeDocFsPath
:
string
,
root
?:
string
):
CompletionItem
[]
{
if
(
startsWith
(
value
,
'
/
'
)
&&
!
root
)
{
/**
* Get a list of path suggestions. Folder suggestions are suffixed with a slash.
*/
function
providePaths
(
valueBeforeCursor
:
string
,
activeDocFsPath
:
string
,
root
?:
string
):
string
[]
{
if
(
startsWith
(
valueBeforeCursor
,
'
/
'
)
&&
!
root
)
{
return
[];
}
let
replaceRange
:
Range
;
const
lastIndexOfSlash
=
value
.
lastIndexOf
(
'
/
'
);
if
(
lastIndexOfSlash
===
-
1
)
{
replaceRange
=
getFullReplaceRange
(
range
);
}
else
{
const
valueAfterLastSlash
=
value
.
slice
(
lastIndexOfSlash
+
1
);
replaceRange
=
getReplaceRange
(
range
,
valueAfterLastSlash
);
}
const
lastIndexOfSlash
=
valueBeforeCursor
.
lastIndexOf
(
'
/
'
);
let
parentDir
:
string
;
if
(
lastIndexOfSlash
===
-
1
)
{
parentDir
=
path
.
resolve
(
root
);
}
else
{
const
valueBeforeLastSlash
=
value
.
slice
(
0
,
lastIndexOfSlash
+
1
);
const
valueBeforeLastSlash
=
value
BeforeCursor
.
slice
(
0
,
lastIndexOfSlash
+
1
);
parentDir
=
startsWith
(
value
,
'
/
'
)
parentDir
=
startsWith
(
value
BeforeCursor
,
'
/
'
)
?
path
.
resolve
(
root
,
'
.
'
+
valueBeforeLastSlash
)
:
path
.
resolve
(
activeDocFsPath
,
'
..
'
,
valueBeforeLastSlash
);
}
try
{
return
fs
.
readdirSync
(
parentDir
).
map
(
f
=>
{
if
(
isDir
(
path
.
resolve
(
parentDir
,
f
)))
{
return
{
label
:
f
+
'
/
'
,
kind
:
CompletionItemKind
.
Folder
,
textEdit
:
TextEdit
.
replace
(
replaceRange
,
f
+
'
/
'
),
command
:
{
title
:
'
Suggest
'
,
command
:
'
editor.action.triggerSuggest
'
}
};
}
else
{
return
{
label
:
f
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
TextEdit
.
replace
(
replaceRange
,
f
)
};
}
return
fs
.
statSync
(
path
.
resolve
(
parentDir
,
f
)).
isDirectory
()
?
f
+
'
/
'
:
f
;
});
}
catch
(
e
)
{
return
[];
}
}
const
isDir
=
(
p
:
string
)
=>
{
return
fs
.
statSync
(
p
).
isDirectory
();
};
function
pathToSuggestion
(
p
:
string
,
valueBeforeCursor
:
string
,
fullValue
:
string
,
range
:
Range
):
CompletionItem
{
const
isDir
=
p
[
p
.
length
-
1
]
===
'
/
'
;
let
replaceRange
:
Range
;
const
lastIndexOfSlash
=
valueBeforeCursor
.
lastIndexOf
(
'
/
'
);
if
(
lastIndexOfSlash
===
-
1
)
{
replaceRange
=
shiftRange
(
range
,
1
,
-
1
);
}
else
{
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
// Find the last slash before cursor, and calculate the start of replace range from there
const
valueAfterLastSlash
=
fullValue
.
slice
(
lastIndexOfSlash
+
1
);
const
startPos
=
shiftPosition
(
range
.
end
,
-
1
-
valueAfterLastSlash
.
length
);
// If whitespace exists, replace until it
const
whiteSpaceIndex
=
valueAfterLastSlash
.
indexOf
(
'
'
);
let
endPos
;
if
(
whiteSpaceIndex
!==
-
1
)
{
endPos
=
shiftPosition
(
startPos
,
whiteSpaceIndex
);
}
else
{
endPos
=
shiftPosition
(
range
.
end
,
-
1
);
}
replaceRange
=
Range
.
create
(
startPos
,
endPos
);
}
if
(
isDir
)
{
return
{
label
:
p
,
kind
:
CompletionItemKind
.
Folder
,
textEdit
:
TextEdit
.
replace
(
replaceRange
,
p
),
command
:
{
title
:
'
Suggest
'
,
command
:
'
editor.action.triggerSuggest
'
}
};
}
else
{
return
{
label
:
p
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
TextEdit
.
replace
(
replaceRange
,
p
)
};
}
}
function
resolveWorkspaceRoot
(
activeDoc
:
TextDocument
,
workspaceFolders
:
WorkspaceFolder
[]):
string
|
undefined
{
for
(
let
i
=
0
;
i
<
workspaceFolders
.
length
;
i
++
)
{
...
...
@@ -112,14 +142,12 @@ function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: Workspa
}
}
function
getFullReplaceRange
(
valueRange
:
Range
)
{
const
start
=
Position
.
create
(
valueRange
.
end
.
line
,
valueRange
.
start
.
character
+
1
);
const
end
=
Position
.
create
(
valueRange
.
end
.
line
,
valueRange
.
end
.
character
-
1
);
return
Range
.
create
(
start
,
end
);
function
shiftPosition
(
pos
:
Position
,
offset
:
number
):
Position
{
return
Position
.
create
(
pos
.
line
,
pos
.
character
+
offset
);
}
function
getReplaceRange
(
valueRange
:
Range
,
valueAfterLastSlash
:
string
)
{
const
start
=
Position
.
create
(
valueRange
.
end
.
line
,
valueRange
.
end
.
character
-
1
-
valueAfterLastSlash
.
length
);
const
end
=
Position
.
create
(
valueRange
.
end
.
line
,
valueRange
.
end
.
character
-
1
);
function
shiftRange
(
range
:
Range
,
startOffset
:
number
,
endOffset
:
number
):
Range
{
const
start
=
shiftPosition
(
range
.
start
,
startOffset
);
const
end
=
shiftPosition
(
range
.
end
,
endOffset
);
return
Range
.
create
(
start
,
end
);
}
...
...
extensions/html-language-features/server/src/test/completions.test.ts
浏览文件 @
5b48bc57
...
...
@@ -7,7 +7,7 @@
import
'
mocha
'
;
import
*
as
assert
from
'
assert
'
;
import
*
as
path
from
'
path
'
;
import
Uri
from
'
vscode-uri
'
;
//
import Uri from 'vscode-uri';
import
{
TextDocument
,
CompletionList
,
CompletionItemKind
}
from
'
vscode-languageserver-types
'
;
import
{
getLanguageModes
}
from
'
../modes/languageModes
'
;
import
{
getPathCompletionParticipant
}
from
'
../modes/pathCompletion
'
;
...
...
@@ -18,117 +18,256 @@ export interface ItemDescription {
documentation
?:
string
;
kind
?:
CompletionItemKind
;
resultText
?:
string
;
command
?:
{
title
:
string
,
command
:
string
};
notAvailable
?:
boolean
;
}
export
function
assertCompletion
(
completions
:
CompletionList
,
expected
:
ItemDescription
,
document
:
TextDocument
,
offset
:
number
)
{
let
matches
=
completions
.
items
.
filter
(
completion
=>
{
return
completion
.
label
===
expected
.
label
;
});
if
(
expected
.
notAvailable
)
{
assert
.
equal
(
matches
.
length
,
0
,
`
${
expected
.
label
}
should not existing is results`
);
return
;
}
suite
(
'
HTML Completions
'
,
()
=>
{
let
assertCompletion
=
function
(
completions
:
CompletionList
,
expected
:
ItemDescription
,
document
:
TextDocument
,
offset
:
number
)
{
let
matches
=
completions
.
items
.
filter
(
completion
=>
{
return
completion
.
label
===
expected
.
label
;
});
if
(
expected
.
notAvailable
)
{
assert
.
equal
(
matches
.
length
,
0
,
`
${
expected
.
label
}
should not existing is results`
);
return
;
}
assert
.
equal
(
matches
.
length
,
1
,
`
${
expected
.
label
}
should only existing once: Actual:
${
completions
.
items
.
map
(
c
=>
c
.
label
).
join
(
'
,
'
)}
`
);
let
match
=
matches
[
0
];
if
(
expected
.
documentation
)
{
assert
.
equal
(
match
.
documentation
,
expected
.
documentation
);
}
if
(
expected
.
kind
)
{
assert
.
equal
(
match
.
kind
,
expected
.
kind
);
}
if
(
expected
.
resultText
&&
match
.
textEdit
)
{
assert
.
equal
(
TextDocument
.
applyEdits
(
document
,
[
match
.
textEdit
]),
expected
.
resultText
);
}
};
assert
.
equal
(
matches
.
length
,
1
,
`
${
expected
.
label
}
should only existing once: Actual:
${
completions
.
items
.
map
(
c
=>
c
.
label
).
join
(
'
,
'
)}
`
);
let
match
=
matches
[
0
];
if
(
expected
.
documentation
)
{
assert
.
equal
(
match
.
documentation
,
expected
.
documentation
);
}
if
(
expected
.
kind
)
{
assert
.
equal
(
match
.
kind
,
expected
.
kind
);
}
if
(
expected
.
resultText
&&
match
.
textEdit
)
{
assert
.
equal
(
TextDocument
.
applyEdits
(
document
,
[
match
.
textEdit
]),
expected
.
resultText
);
}
if
(
expected
.
command
)
{
assert
.
deepEqual
(
match
.
command
,
expected
.
command
);
}
}
const
testUri
=
'
test://test/test.html
'
;
const
testUri
=
'
test://test/test.html
'
;
function
assertCompletions
(
value
:
string
,
expected
:
{
count
?:
number
,
items
?:
ItemDescription
[]
},
uri
=
testUri
,
workspaceFolders
?:
WorkspaceFolder
[]):
void
{
let
offset
=
value
.
indexOf
(
'
|
'
);
value
=
value
.
substr
(
0
,
offset
)
+
value
.
substr
(
offset
+
1
);
export
function
testCompletionFor
(
value
:
string
,
expected
:
{
count
?:
number
,
items
?:
ItemDescription
[]
},
uri
=
testUri
,
workspaceFolders
?:
WorkspaceFolder
[]
):
void
{
let
offset
=
value
.
indexOf
(
'
|
'
);
value
=
value
.
substr
(
0
,
offset
)
+
value
.
substr
(
offset
+
1
);
let
document
=
TextDocument
.
create
(
uri
,
'
html
'
,
0
,
value
);
let
position
=
document
.
positionAt
(
offset
);
let
document
=
TextDocument
.
create
(
uri
,
'
html
'
,
0
,
value
);
let
position
=
document
.
positionAt
(
offset
);
var
languageModes
=
getLanguageModes
({
css
:
true
,
javascript
:
true
});
var
mode
=
languageModes
.
getModeAtPosition
(
document
,
position
)
!
;
var
languageModes
=
getLanguageModes
({
css
:
true
,
javascript
:
true
});
var
mode
=
languageModes
.
getModeAtPosition
(
document
,
position
)
!
;
if
(
!
workspaceFolders
)
{
workspaceFolders
=
[{
name
:
'
x
'
,
uri
:
path
.
dirname
(
uri
)
}];
}
if
(
!
workspaceFolders
)
{
workspaceFolders
=
[{
name
:
'
x
'
,
uri
:
path
.
dirname
(
uri
)
}];
}
let
participantResult
=
CompletionList
.
create
([]);
if
(
mode
.
setCompletionParticipants
)
{
mode
.
setCompletionParticipants
([
getPathCompletionParticipant
(
document
,
workspaceFolders
,
participantResult
)]);
}
let
participantResult
=
CompletionList
.
create
([]);
if
(
mode
.
setCompletionParticipants
)
{
mode
.
setCompletionParticipants
([
getPathCompletionParticipant
(
document
,
workspaceFolders
,
participantResult
)]);
}
let
list
=
mode
.
doComplete
!
(
document
,
position
)
!
;
list
.
items
=
list
.
items
.
concat
(
participantResult
.
items
);
let
list
=
mode
.
doComplete
!
(
document
,
position
)
!
;
list
.
items
=
list
.
items
.
concat
(
participantResult
.
items
);
if
(
expected
.
count
)
{
assert
.
equal
(
list
.
items
,
expected
.
count
);
}
if
(
expected
.
items
)
{
for
(
let
item
of
expected
.
items
)
{
assertCompletion
(
list
,
item
,
document
,
offset
);
}
if
(
expected
.
count
)
{
assert
.
equal
(
list
.
items
,
expected
.
count
);
}
if
(
expected
.
items
)
{
for
(
let
item
of
expected
.
items
)
{
assertCompletion
(
list
,
item
,
document
,
offset
);
}
}
}
suite
(
'
HTML Completion
'
,
()
=>
{
test
(
'
HTML Javascript Completions
'
,
function
():
any
{
assertCompletions
(
'
<html><script>window.|</script></html>
'
,
{
testCompletionFor
(
'
<html><script>window.|</script></html>
'
,
{
items
:
[
{
label
:
'
location
'
,
resultText
:
'
<html><script>window.location</script></html>
'
},
]
});
assertCompletions
(
'
<html><script>$.|</script></html>
'
,
{
testCompletionFor
(
'
<html><script>$.|</script></html>
'
,
{
items
:
[
{
label
:
'
getJSON
'
,
resultText
:
'
<html><script>$.getJSON</script></html>
'
},
]
});
});
});
suite
(
'
HTML Path Completion
'
,
()
=>
{
const
triggerSuggestCommand
=
{
title
:
'
Suggest
'
,
command
:
'
editor.action.triggerSuggest
'
};
test
(
'
Path completion
'
,
function
():
any
{
let
testUri
=
Uri
.
file
(
path
.
resolve
(
__dirname
,
'
../../test/pathCompletionFixtures/foo.html
'
)).
fsPath
;
const
fixtureRoot
=
path
.
resolve
(
__dirname
,
'
pathcompletionfixtures
'
);
const
fixtureWorkspace
=
{
name
:
'
fixture
'
,
uri
:
fixtureRoot
};
const
indexHtmlUri
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
aboutHtmlUri
=
path
.
resolve
(
fixtureRoot
,
'
about/about.html
'
);
assertCompletions
(
'
<div><a href="about/|">
'
,
{
test
(
'
Basics - Correct label/kind/result/command
'
,
()
=>
{
testCompletionFor
(
'
<script src="./|">
'
,
{
items
:
[
{
label
:
'
about.html
'
,
resultText
:
'
<div><a href="about/about.html">
'
}
{
label
:
'
about/
'
,
kind
:
CompletionItemKind
.
Folder
,
resultText
:
'
<script src="./about/">
'
,
command
:
triggerSuggestCommand
},
{
label
:
'
index.html
'
,
kind
:
CompletionItemKind
.
File
,
resultText
:
'
<script src="./index.html">
'
},
{
label
:
'
src/
'
,
kind
:
CompletionItemKind
.
Folder
,
resultText
:
'
<script src="./src/">
'
,
command
:
triggerSuggestCommand
}
]
},
testUri
);
// Unquoted value is not supported in language service yet
// assertCompletions(`<div><a href=about/|>`, {
// items: [
// { label: 'about.html', resultText: `<div><a href=about/about.html>` }
// ]
// }, testUri);
assertCompletions
(
`<div><a href='about/|'>`
,
{
},
indexHtmlUri
);
});
test
(
'
Basics - Single Quote
'
,
()
=>
{
testCompletionFor
(
`<script src='./|'>`
,
{
items
:
[
{
label
:
'
about.html
'
,
resultText
:
`<div><a href='about/about.html'>`
}
{
label
:
'
about/
'
,
kind
:
CompletionItemKind
.
Folder
,
resultText
:
`<script src='./about/'>`
,
command
:
triggerSuggestCommand
},
{
label
:
'
index.html
'
,
kind
:
CompletionItemKind
.
File
,
resultText
:
`<script src='./index.html'>`
},
{
label
:
'
src/
'
,
kind
:
CompletionItemKind
.
Folder
,
resultText
:
`<script src='./src/'>`
,
command
:
triggerSuggestCommand
}
]
},
testUri
);
// Don't think this is a common use case
// assertCompletions('<div><a href="about/about|.xml">', {
// items: [
// { label: 'about.html', resultText: '<div><a href="about/about.html">' }
// ]
// }, testUri);
assertCompletions
(
'
<div><a href="about/a|">
'
,
{
},
indexHtmlUri
);
});
test
(
'
No completion for remote paths
'
,
()
=>
{
testCompletionFor
(
'
<script src="http:">
'
,
{
items
:
[]
});
testCompletionFor
(
'
<script src="http:/|">
'
,
{
items
:
[]
});
testCompletionFor
(
'
<script src="http://|">
'
,
{
items
:
[]
});
testCompletionFor
(
'
<script src="https:|">
'
,
{
items
:
[]
});
testCompletionFor
(
'
<script src="https:/|">
'
,
{
items
:
[]
});
testCompletionFor
(
'
<script src="https://|">
'
,
{
items
:
[]
});
testCompletionFor
(
'
<script src="//|">
'
,
{
items
:
[]
});
});
test
(
'
Relative Path
'
,
()
=>
{
testCompletionFor
(
'
<script src="../|">
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="../about/">
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="../index.html">
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="../src/">
'
}
]
},
aboutHtmlUri
);
testCompletionFor
(
'
<script src="../src/|">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="../src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="../src/test.js">
'
},
]
},
aboutHtmlUri
);
});
test
(
'
Absolute Path
'
,
()
=>
{
testCompletionFor
(
'
<script src="/|">
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="/about/">
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="/index.html">
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="/src/">
'
},
]
},
indexHtmlUri
);
testCompletionFor
(
'
<script src="/src/|">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="/src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="/src/test.js">
'
},
]
},
aboutHtmlUri
,
[
fixtureWorkspace
]);
});
test
(
'
Empty Path Value
'
,
()
=>
{
testCompletionFor
(
'
<script src="|">
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="about/">
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="index.html">
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="src/">
'
},
]
},
indexHtmlUri
);
});
test
(
'
Incomplete Path
'
,
()
=>
{
testCompletionFor
(
'
<script src="/src/f|">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="/src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="/src/test.js">
'
},
]
},
aboutHtmlUri
,
[
fixtureWorkspace
]);
testCompletionFor
(
'
<script src="../src/f|">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="../src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="../src/test.js">
'
},
]
},
aboutHtmlUri
,
[
fixtureWorkspace
]);
});
test
(
'
No leading dot or slash
'
,
()
=>
{
testCompletionFor
(
'
<script src="s|">
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="about/">
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="index.html">
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="src/">
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
testCompletionFor
(
'
<script src="src/|">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="src/test.js">
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
testCompletionFor
(
'
<script src="src/f|">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="src/test.js">
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
});
test
(
'
Trigger completion in middle of path
'
,
()
=>
{
testCompletionFor
(
'
<script src="src/f|eature.js">
'
,
{
items
:
[
{
label
:
'
feature.js
'
,
resultText
:
'
<script src="src/feature.js">
'
},
{
label
:
'
test.js
'
,
resultText
:
'
<script src="src/test.js">
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
testCompletionFor
(
'
<script src="s|rc/feature.js">
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="about/">
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="index.html">
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="src/">
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
});
test
(
'
Trigger completion in middle of path and with whitespaces
'
,
()
=>
{
testCompletionFor
(
'
<script src="./| about/about.html>
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="./about/ about/about.html>
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="./index.html about/about.html>
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="./src/ about/about.html>
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
testCompletionFor
(
'
<script src="./a|bout /about.html>
'
,
{
items
:
[
{
label
:
'
about/
'
,
resultText
:
'
<script src="./about/ /about.html>
'
},
{
label
:
'
index.html
'
,
resultText
:
'
<script src="./index.html /about.html>
'
},
{
label
:
'
src/
'
,
resultText
:
'
<script src="./src/ /about.html>
'
},
]
},
indexHtmlUri
,
[
fixtureWorkspace
]);
});
test
(
'
Unquoted Path
'
,
()
=>
{
/* Unquoted value is not supported in html language service yet
testCompletionFor(`<div><a href=about/|>`, {
items: [
{
label
:
'
about.html
'
,
resultText
:
'
<div><a href="about/about.html">
'
}
{ label: 'about.html', resultText:
`<div><a href=about/about.html>`
}
]
}, testUri);
// We should not prompt suggestion before user enters any trigger character
// assertCompletions('<div><a href="|">', {
// items: [
// { label: 'index.html', resultText: '<div><a href="index.html">' },
// { label: 'about', resultText: '<div><a href="about/">' }
// ]
// }, testUri);
*/
});
});
\ No newline at end of file
});
extensions/html-language-features/server/src/test/pathCompletion/pathCompletion.test.ts
已删除
100644 → 0
浏览文件 @
d6bdff7f
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'
use strict
'
;
import
*
as
assert
from
'
assert
'
;
import
*
as
path
from
'
path
'
;
import
{
providePathSuggestions
}
from
'
../../modes/pathCompletion
'
;
import
{
CompletionItemKind
,
Range
,
Position
,
CompletionItem
,
TextEdit
,
Command
}
from
'
vscode-languageserver-types
'
;
const
fixtureRoot
=
path
.
resolve
(
__dirname
,
'
../../../test/pathCompletionFixtures
'
);
function
toRange
(
line
:
number
,
startChar
:
number
,
endChar
:
number
)
{
return
Range
.
create
(
Position
.
create
(
line
,
startChar
),
Position
.
create
(
line
,
endChar
));
}
function
toTextEdit
(
line
:
number
,
startChar
:
number
,
endChar
:
number
,
newText
:
string
)
{
const
range
=
Range
.
create
(
Position
.
create
(
line
,
startChar
),
Position
.
create
(
line
,
endChar
));
return
TextEdit
.
replace
(
range
,
newText
);
}
interface
PathSuggestion
{
label
?:
string
;
kind
?:
CompletionItemKind
;
textEdit
?:
TextEdit
;
command
?:
Command
;
}
function
assertSuggestions
(
actual
:
CompletionItem
[],
expected
:
PathSuggestion
[])
{
assert
.
equal
(
actual
.
length
,
expected
.
length
,
`Suggestions have length
${
actual
.
length
}
but should have length
${
expected
.
length
}
`
);
for
(
let
i
=
0
;
i
<
expected
.
length
;
i
++
)
{
if
(
expected
[
i
].
label
)
{
assert
.
equal
(
actual
[
i
].
label
,
expected
[
i
].
label
,
`Suggestion
${
actual
[
i
].
label
}
should have label
${
expected
[
i
].
label
}
`
);
}
if
(
expected
[
i
].
kind
)
{
assert
.
equal
(
actual
[
i
].
kind
,
expected
[
i
].
kind
,
`Suggestion
${
actual
[
i
].
label
}
has kind
${
actual
[
i
].
kind
}
but should have
${
expected
[
i
].
kind
}
`
);
}
if
(
expected
[
i
].
textEdit
)
{
assert
.
equal
(
actual
[
i
].
textEdit
!
.
newText
,
expected
[
i
].
textEdit
!
.
newText
);
assert
.
deepEqual
(
actual
[
i
].
textEdit
!
.
range
,
expected
[
i
].
textEdit
!
.
range
);
}
if
(
expected
[
i
].
command
)
{
assert
.
equal
(
actual
[
i
].
command
!
.
title
,
expected
[
i
].
command
!
.
title
,
`Suggestion
${
actual
[
i
].
label
}
has command title
${
actual
[
i
].
command
!
.
title
}
but should have command title
${
expected
[
i
].
command
!
.
title
}
`
);
assert
.
equal
(
actual
[
i
].
command
!
.
command
,
expected
[
i
].
command
!
.
command
,
`Suggestion
${
actual
[
i
].
label
}
has command
${
actual
[
i
].
command
!
.
command
}
but should have command
${
expected
[
i
].
command
!
.
command
}
`
);
}
}
}
suite
(
'
Path Completion - Relative Path:
'
,
()
=>
{
const
mockRange
=
toRange
(
0
,
3
,
5
);
test
(
'
Current Folder
'
,
()
=>
{
const
value
=
'
./
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
about/
'
,
kind
:
CompletionItemKind
.
Folder
},
{
label
:
'
index.html
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
src/
'
,
kind
:
CompletionItemKind
.
Folder
}
]);
});
test
(
'
Parent Folder:
'
,
()
=>
{
const
value
=
'
../
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
about/about.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
about/
'
,
kind
:
CompletionItemKind
.
Folder
},
{
label
:
'
index.html
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
src/
'
,
kind
:
CompletionItemKind
.
Folder
}
]);
});
test
(
'
Adjacent Folder:
'
,
()
=>
{
const
value
=
'
../src/
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
about/about.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
feature.js
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
test.js
'
,
kind
:
CompletionItemKind
.
File
}
]);
});
});
suite
(
'
Path Completion - Absolute Path:
'
,
()
=>
{
const
mockRange
=
toRange
(
0
,
3
,
5
);
test
(
'
Root
'
,
()
=>
{
const
value
=
'
/
'
;
const
activeFileFsPath1
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
activeFileFsPath2
=
path
.
resolve
(
fixtureRoot
,
'
about/index.html
'
);
const
suggestions1
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath1
,
fixtureRoot
);
const
suggestions2
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath2
,
fixtureRoot
);
const
verify
=
(
suggestions
:
CompletionItem
[])
=>
{
assertSuggestions
(
suggestions
,
[
{
label
:
'
about/
'
,
kind
:
CompletionItemKind
.
Folder
},
{
label
:
'
index.html
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
src/
'
,
kind
:
CompletionItemKind
.
Folder
}
]);
};
verify
(
suggestions1
);
verify
(
suggestions2
);
});
test
(
'
Sub Folder
'
,
()
=>
{
const
value
=
'
/src/
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
about/about.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
,
fixtureRoot
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
feature.js
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
test.js
'
,
kind
:
CompletionItemKind
.
File
}
]);
});
});
suite
(
'
Path Completion - Folder Commands:
'
,
()
=>
{
const
mockRange
=
toRange
(
0
,
3
,
5
);
test
(
'
Folder should have command `editor.action.triggerSuggest
'
,
()
=>
{
const
value
=
'
./
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
about/
'
,
command
:
{
title
:
'
Suggest
'
,
command
:
'
editor.action.triggerSuggest
'
}
},
{
label
:
'
index.html
'
},
{
label
:
'
src/
'
,
command
:
{
title
:
'
Suggest
'
,
command
:
'
editor.action.triggerSuggest
'
}
},
]);
});
});
suite
(
'
Path Completion - Incomplete Path at End:
'
,
()
=>
{
const
mockRange
=
toRange
(
0
,
3
,
5
);
test
(
'
Incomplete Path that starts with slash
'
,
()
=>
{
const
value
=
'
/src/f
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
about/about.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
,
fixtureRoot
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
feature.js
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
test.js
'
,
kind
:
CompletionItemKind
.
File
}
]);
});
test
(
'
Incomplete Path that does not start with slash
'
,
()
=>
{
const
value
=
'
../src/f
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
about/about.html
'
);
const
suggestions
=
providePathSuggestions
(
value
,
mockRange
,
activeFileFsPath
,
fixtureRoot
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
feature.js
'
,
kind
:
CompletionItemKind
.
File
},
{
label
:
'
test.js
'
,
kind
:
CompletionItemKind
.
File
}
]);
});
});
suite
(
'
Path Completion - No leading dot or slash:
'
,
()
=>
{
test
(
'
Top level completion
'
,
()
=>
{
const
value
=
'
s
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
range
=
toRange
(
0
,
3
,
5
);
const
suggestions
=
providePathSuggestions
(
value
,
range
,
activeFileFsPath
,
fixtureRoot
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
about/
'
,
kind
:
CompletionItemKind
.
Folder
,
textEdit
:
toTextEdit
(
0
,
4
,
4
,
'
about/
'
)
},
{
label
:
'
index.html
'
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
toTextEdit
(
0
,
4
,
4
,
'
index.html
'
)
},
{
label
:
'
src/
'
,
kind
:
CompletionItemKind
.
Folder
,
textEdit
:
toTextEdit
(
0
,
4
,
4
,
'
src/
'
)
}
]);
});
test
(
'
src/
'
,
()
=>
{
const
value
=
'
src/
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
range
=
toRange
(
0
,
3
,
8
);
const
suggestions
=
providePathSuggestions
(
value
,
range
,
activeFileFsPath
,
fixtureRoot
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
feature.js
'
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
toTextEdit
(
0
,
7
,
7
,
'
feature.js
'
)
},
{
label
:
'
test.js
'
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
toTextEdit
(
0
,
7
,
7
,
'
test.js
'
)
}
]);
});
test
(
'
src/f
'
,
()
=>
{
const
value
=
'
src/f
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
range
=
toRange
(
0
,
3
,
9
);
const
suggestions
=
providePathSuggestions
(
value
,
range
,
activeFileFsPath
,
fixtureRoot
);
assertSuggestions
(
suggestions
,
[
{
label
:
'
feature.js
'
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
toTextEdit
(
0
,
7
,
8
,
'
feature.js
'
)
},
{
label
:
'
test.js
'
,
kind
:
CompletionItemKind
.
File
,
textEdit
:
toTextEdit
(
0
,
7
,
8
,
'
test.js
'
)
}
]);
});
});
suite
(
'
Path Completion - TextEdit:
'
,
()
=>
{
test
(
'
TextEdit has correct replace text and range
'
,
()
=>
{
const
value
=
'
./
'
;
const
activeFileFsPath
=
path
.
resolve
(
fixtureRoot
,
'
index.html
'
);
const
range
=
toRange
(
0
,
3
,
5
);
const
expectedReplaceRange
=
toRange
(
0
,
4
,
4
);
const
suggestions
=
providePathSuggestions
(
value
,
range
,
activeFileFsPath
);
assertSuggestions
(
suggestions
,
[
{
textEdit
:
TextEdit
.
replace
(
expectedReplaceRange
,
'
about/
'
)
},
{
textEdit
:
TextEdit
.
replace
(
expectedReplaceRange
,
'
index.html
'
)
},
{
textEdit
:
TextEdit
.
replace
(
expectedReplaceRange
,
'
src/
'
)
},
]);
});
});
extensions/html-language-features/server/test/pathCompletionFixtures/about/about.css
→
extensions/html-language-features/server/
src/
test/pathCompletionFixtures/about/about.css
浏览文件 @
5b48bc57
文件已移动
extensions/html-language-features/server/test/pathCompletionFixtures/about/about.html
→
extensions/html-language-features/server/
src/
test/pathCompletionFixtures/about/about.html
浏览文件 @
5b48bc57
文件已移动
extensions/html-language-features/server/test/pathCompletionFixtures/index.html
→
extensions/html-language-features/server/
src/
test/pathCompletionFixtures/index.html
浏览文件 @
5b48bc57
文件已移动
extensions/html-language-features/server/test/pathCompletionFixtures/src/feature.js
→
extensions/html-language-features/server/
src/
test/pathCompletionFixtures/src/feature.js
浏览文件 @
5b48bc57
文件已移动
extensions/html-language-features/server/test/pathCompletionFixtures/src/test.js
→
extensions/html-language-features/server/
src/
test/pathCompletionFixtures/src/test.js
浏览文件 @
5b48bc57
文件已移动
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录