@@ -19,22 +19,32 @@ export function getPathCompletionParticipant(
19
19
result : CompletionList
20
20
) : ICompletionParticipant {
21
21
return {
22
- onHtmlAttributeValue : ( { tag, attribute, value, range } ) => {
22
+ onHtmlAttributeValue : ( { tag, position, attribute, value : valueBeforeCursor , range } ) => {
23
+ const fullValue = getFullValueWithoutQuotes ( document , range ) ;
23
24
24
- if ( shouldDoPathCompletion ( tag , attribute , value ) ) {
25
+ if ( shouldDoPathCompletion ( tag , attribute , fullValue ) ) {
25
26
if ( ! workspaceFolders || workspaceFolders . length === 0 ) {
26
27
return ;
27
28
}
28
29
const workspaceRoot = resolveWorkspaceRoot ( document , workspaceFolders ) ;
29
30
30
- const paths = providePaths ( value , URI . parse ( document . uri ) . fsPath , workspaceRoot ) ;
31
- const suggestions = paths . map ( p => pathToSuggestion ( p , value , range ) ) ;
31
+ const paths = providePaths ( valueBeforeCursor , URI . parse ( document . uri ) . fsPath , workspaceRoot ) ;
32
+ const suggestions = paths . map ( p => pathToSuggestion ( p , valueBeforeCursor , fullValue , range ) ) ;
32
33
result . items = [ ...suggestions , ...result . items ] ;
33
34
}
34
35
}
35
36
} ;
36
37
}
37
38
39
+ function getFullValueWithoutQuotes ( document : TextDocument , range : Range ) {
40
+ const fullValue = document . getText ( range ) ;
41
+ if ( startsWith ( fullValue , `'` ) || startsWith ( fullValue , `"` ) ) {
42
+ return fullValue . slice ( 1 , - 1 ) ;
43
+ } else {
44
+ return fullValue ;
45
+ }
46
+ }
47
+
38
48
function shouldDoPathCompletion ( tag : string , attr : string , value : string ) : boolean {
39
49
if ( startsWith ( value , 'http' ) || startsWith ( value , 'https' ) || startsWith ( value , '//' ) ) {
40
50
return false ;
@@ -54,19 +64,19 @@ function shouldDoPathCompletion(tag: string, attr: string, value: string): boole
54
64
/**
55
65
* Get a list of path suggestions. Folder suggestions are suffixed with a slash.
56
66
*/
57
- function providePaths ( value : string , activeDocFsPath : string , root ?: string ) : string [ ] {
58
- if ( startsWith ( value , '/' ) && ! root ) {
67
+ function providePaths ( valueBeforeCursor : string , activeDocFsPath : string , root ?: string ) : string [ ] {
68
+ if ( startsWith ( valueBeforeCursor , '/' ) && ! root ) {
59
69
return [ ] ;
60
70
}
61
71
62
- const lastIndexOfSlash = value . lastIndexOf ( '/' ) ;
72
+ const lastIndexOfSlash = valueBeforeCursor . lastIndexOf ( '/' ) ;
63
73
let parentDir : string ;
64
74
if ( lastIndexOfSlash === - 1 ) {
65
75
parentDir = path . resolve ( root ) ;
66
76
} else {
67
- const valueBeforeLastSlash = value . slice ( 0 , lastIndexOfSlash + 1 ) ;
77
+ const valueBeforeLastSlash = valueBeforeCursor . slice ( 0 , lastIndexOfSlash + 1 ) ;
68
78
69
- parentDir = startsWith ( value , '/' )
79
+ parentDir = startsWith ( valueBeforeCursor , '/' )
70
80
? path . resolve ( root , '.' + valueBeforeLastSlash )
71
81
: path . resolve ( activeDocFsPath , '..' , valueBeforeLastSlash ) ;
72
82
}
@@ -82,16 +92,27 @@ function providePaths(value: string, activeDocFsPath: string, root?: string): st
82
92
}
83
93
}
84
94
85
- function pathToSuggestion ( p : string , value : string , range : Range ) : CompletionItem {
95
+ function pathToSuggestion ( p : string , valueBeforeCursor : string , fullValue : string , range : Range ) : CompletionItem {
86
96
const isDir = p [ p . length - 1 ] === '/' ;
87
97
88
98
let replaceRange : Range ;
89
- const lastIndexOfSlash = value . lastIndexOf ( '/' ) ;
99
+ const lastIndexOfSlash = valueBeforeCursor . lastIndexOf ( '/' ) ;
90
100
if ( lastIndexOfSlash === - 1 ) {
91
- replaceRange = getFullReplaceRange ( range ) ;
101
+ replaceRange = shiftRange ( range , 1 , - 1 ) ;
92
102
} else {
93
- const valueAfterLastSlash = value . slice ( lastIndexOfSlash + 1 ) ;
94
- replaceRange = getReplaceRange ( range , valueAfterLastSlash ) ;
103
+ // For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
104
+ // Find the last slash before cursor, and calculate the start of replace range from there
105
+ const valueAfterLastSlash = fullValue . slice ( lastIndexOfSlash + 1 ) ;
106
+ const startPos = shiftPosition ( range . end , - 1 - valueAfterLastSlash . length ) ;
107
+ // If whitespace exists, replace until it
108
+ const whiteSpaceIndex = valueAfterLastSlash . indexOf ( ' ' ) ;
109
+ let endPos ;
110
+ if ( whiteSpaceIndex !== - 1 ) {
111
+ endPos = shiftPosition ( startPos , whiteSpaceIndex ) ;
112
+ } else {
113
+ endPos = shiftPosition ( range . end , - 1 ) ;
114
+ }
115
+ replaceRange = Range . create ( startPos , endPos ) ;
95
116
}
96
117
97
118
if ( isDir ) {
@@ -121,14 +142,12 @@ function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: Workspa
121
142
}
122
143
}
123
144
124
- function getFullReplaceRange ( valueRange : Range ) {
125
- const start = Position . create ( valueRange . end . line , valueRange . start . character + 1 ) ;
126
- const end = Position . create ( valueRange . end . line , valueRange . end . character - 1 ) ;
127
- return Range . create ( start , end ) ;
145
+ function shiftPosition ( pos : Position , offset : number ) : Position {
146
+ return Position . create ( pos . line , pos . character + offset ) ;
128
147
}
129
- function getReplaceRange ( valueRange : Range , valueAfterLastSlash : string ) {
130
- const start = Position . create ( valueRange . end . line , valueRange . end . character - 1 - valueAfterLastSlash . length ) ;
131
- const end = Position . create ( valueRange . end . line , valueRange . end . character - 1 ) ;
148
+ function shiftRange ( range : Range , startOffset : number , endOffset : number ) : Range {
149
+ const start = shiftPosition ( range . start , startOffset ) ;
150
+ const end = shiftPosition ( range . end , endOffset ) ;
132
151
return Range . create ( start , end ) ;
133
152
}
134
153
0 commit comments