1
1
import get from 'lodash.get'
2
2
import type tslib from 'typescript/lib/tsserverlibrary'
3
+ import * as emmet from '@vscode/emmet-helper'
3
4
4
5
//@ts -ignore
5
6
import type { Configuration } from '../../src/configurationType'
6
7
7
8
export = function ( { typescript } : { typescript : typeof import ( 'typescript/lib/tsserverlibrary' ) } ) {
9
+ const ts = typescript
8
10
let _configuration : Configuration
9
11
const c = < T extends keyof Configuration > ( key : T ) : Configuration [ T ] => get ( _configuration , key )
10
12
@@ -22,7 +24,7 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
22
24
proxy [ k ] = ( ...args : Array < Record < string , unknown > > ) => x . apply ( info . languageService , args )
23
25
}
24
26
25
- let prevCompletionsMap : any
27
+ let prevCompletionsMap : Record < string , { originalName : string } >
26
28
proxy . getCompletionsAtPosition = ( fileName , position , options ) => {
27
29
prevCompletionsMap = { }
28
30
if ( ! _configuration ) {
@@ -40,24 +42,38 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
40
42
// 'raw prior',
41
43
// prior?.entries.map(entry => entry.name),
42
44
// )
43
- const node = findChildContainingPosition ( typescript , sourceFile , position )
44
- if ( node ) {
45
- if ( c ( 'jsxPseudoEmmet.enable' ) && ( typescript . isJsxElement ( node ) || ( node . parent && typescript . isJsxElement ( node . parent ) ) ) ) {
46
- if ( typescript . isJsxOpeningElement ( node ) ) {
47
- const nodeText = node . getText ( ) . slice ( 0 , position - node . pos )
48
- if ( c ( 'jsxImproveElementsSuggestions.enabled' ) && ! nodeText . includes ( ' ' ) && prior ) {
49
- let lastPart = nodeText . split ( '.' ) . at ( - 1 ) !
50
- if ( lastPart . startsWith ( '<' ) ) lastPart = lastPart . slice ( 1 )
51
- const isStartingWithUpperCase = ( str : string ) => str [ 0 ] === str [ 0 ] ?. toUpperCase ( )
52
- // check if starts with lowercase
53
- if ( isStartingWithUpperCase ( lastPart ) ) {
54
- // TODO! compare with suggestions from lib.dom
55
- prior . entries = prior . entries . filter (
56
- entry => isStartingWithUpperCase ( entry . name ) && ! [ typescript . ScriptElementKind . enumElement ] . includes ( entry . kind ) ,
57
- )
58
- }
45
+ if ( [ '.jsx' , '.tsx' ] . some ( ext => fileName . endsWith ( ext ) ) ) {
46
+ // JSX Features
47
+ const node = findChildContainingPosition ( typescript , sourceFile , position )
48
+ if ( node ) {
49
+ const { SyntaxKind } = ts
50
+ const emmetSyntaxKinds = [ SyntaxKind . JsxFragment , SyntaxKind . JsxElement , SyntaxKind . JsxText ]
51
+ const emmetClosingSyntaxKinds = [ SyntaxKind . JsxClosingElement , SyntaxKind . JsxClosingFragment ]
52
+ // TODO maybe allow fragment?
53
+ const correntComponentSuggestionsKinds = [ SyntaxKind . JsxOpeningElement , SyntaxKind . JsxSelfClosingElement ]
54
+ const nodeText = node . getFullText ( ) . slice ( 0 , position - node . pos )
55
+ if (
56
+ correntComponentSuggestionsKinds . includes ( node . kind ) &&
57
+ c ( 'jsxImproveElementsSuggestions.enabled' ) &&
58
+ ! nodeText . includes ( ' ' ) &&
59
+ prior
60
+ ) {
61
+ let lastPart = nodeText . split ( '.' ) . at ( - 1 ) !
62
+ if ( lastPart . startsWith ( '<' ) ) lastPart = lastPart . slice ( 1 )
63
+ const isStartingWithUpperCase = ( str : string ) => str [ 0 ] === str [ 0 ] ?. toUpperCase ( )
64
+ // check if starts with lowercase
65
+ if ( isStartingWithUpperCase ( lastPart ) ) {
66
+ // TODO! compare with suggestions from lib.dom
67
+ prior . entries = prior . entries . filter (
68
+ entry => isStartingWithUpperCase ( entry . name ) && ! [ typescript . ScriptElementKind . enumElement ] . includes ( entry . kind ) ,
69
+ )
59
70
}
60
- } else if ( ! typescript . isJsxClosingElement ( node ) /* TODO! scriptSnapshot.getText(position - 1, position).match(/(\s|\w|>)/) */ ) {
71
+ }
72
+ if (
73
+ c ( 'jsxEmmet.type' ) !== 'disabled' &&
74
+ ( emmetSyntaxKinds . includes ( node . kind ) ||
75
+ /* Just before closing tag */ ( emmetClosingSyntaxKinds . includes ( node . kind ) && nodeText . length === 0 ) )
76
+ ) {
61
77
// const { textSpan } = proxy.getSmartSelectionRange(fileName, position)
62
78
// let existing = scriptSnapshot.getText(textSpan.start, textSpan.start + textSpan.length)
63
79
// if (existing.includes('\n')) existing = ''
@@ -72,18 +88,47 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
72
88
// isSnippet: true,
73
89
// })
74
90
// } else if (!existing[0] || existing[0].match(/\w/)) {
75
- const tags = c ( 'jsxPseudoEmmet.tags' )
76
- for ( let [ tag , value ] of Object . entries ( tags ) ) {
77
- if ( value === true ) value = `<${ tag } >$1</${ tag } >`
78
- prior . entries . push ( {
79
- kind : typescript . ScriptElementKind . label ,
80
- name : tag ,
81
- sortText : '!5' ,
82
- insertText : value ,
83
- isSnippet : true ,
84
- } )
91
+ if ( c ( 'jsxEmmet.type' ) === 'realEmmet' ) {
92
+ const sendToEmmet = nodeText . split ( ' ' ) . at ( - 1 ) !
93
+ const emmetCompletions = emmet . doComplete (
94
+ {
95
+ getText : ( ) => sendToEmmet ,
96
+ languageId : 'html' ,
97
+ lineCount : 1 ,
98
+ offsetAt : position => position . character ,
99
+ positionAt : offset => ( { line : 0 , character : offset } ) ,
100
+ uri : '/' ,
101
+ version : 1 ,
102
+ } ,
103
+ { line : 0 , character : sendToEmmet . length } ,
104
+ 'html' ,
105
+ { } ,
106
+ ) ?? { items : [ ] }
107
+ for ( const completion of emmetCompletions . items ) {
108
+ prior . entries . push ( {
109
+ kind : typescript . ScriptElementKind . label ,
110
+ name : completion . label . slice ( 1 ) ,
111
+ sortText : '!5' ,
112
+ // insertText: `${completion.label.slice(1)} ${completion.textEdit?.newText}`,
113
+ insertText : completion . textEdit ?. newText ,
114
+ isSnippet : true ,
115
+ sourceDisplay : completion . detail !== undefined ? [ { kind : 'text' , text : completion . detail } ] : undefined ,
116
+ // replacementSpan: { start: position - 5, length: 5 },
117
+ } )
118
+ }
119
+ } else {
120
+ const tags = c ( 'jsxPseudoEmmet.tags' )
121
+ for ( let [ tag , value ] of Object . entries ( tags ) ) {
122
+ if ( value === true ) value = `<${ tag } >$1</${ tag } >`
123
+ prior . entries . push ( {
124
+ kind : typescript . ScriptElementKind . label ,
125
+ name : tag ,
126
+ sortText : '!5' ,
127
+ insertText : value ,
128
+ isSnippet : true ,
129
+ } )
130
+ }
85
131
}
86
- // }
87
132
}
88
133
}
89
134
}
@@ -107,7 +152,9 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
107
152
prior . entries = prior . entries . map ( entry => {
108
153
if ( ! standardProps . includes ( entry . name ) ) {
109
154
const newName = `☆${ entry . name } `
110
- prevCompletionsMap [ newName ] = entry . name
155
+ prevCompletionsMap [ newName ] = {
156
+ originalName : entry . name ,
157
+ }
111
158
return {
112
159
...entry ,
113
160
insertText : entry . insertText ?? entry . name ,
@@ -176,7 +223,7 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
176
223
const prior = info . languageService . getCompletionEntryDetails (
177
224
fileName ,
178
225
position ,
179
- prevCompletionsMap [ entryName ] || entryName ,
226
+ prevCompletionsMap [ entryName ] ?. originalName || entryName ,
180
227
formatOptions ,
181
228
source ,
182
229
preferences ,
0 commit comments