11import get from 'lodash.get'
22import type tslib from 'typescript/lib/tsserverlibrary'
3+ import * as emmet from '@vscode/emmet-helper'
34
45//@ts -ignore
56import type { Configuration } from '../../src/configurationType'
67
78export = function ( { typescript } : { typescript : typeof import ( 'typescript/lib/tsserverlibrary' ) } ) {
9+ const ts = typescript
810 let _configuration : Configuration
911 const c = < T extends keyof Configuration > ( key : T ) : Configuration [ T ] => get ( _configuration , key )
1012
@@ -22,7 +24,7 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
2224 proxy [ k ] = ( ...args : Array < Record < string , unknown > > ) => x . apply ( info . languageService , args )
2325 }
2426
25- let prevCompletionsMap : any
27+ let prevCompletionsMap : Record < string , { originalName : string } >
2628 proxy . getCompletionsAtPosition = ( fileName , position , options ) => {
2729 prevCompletionsMap = { }
2830 if ( ! _configuration ) {
@@ -40,24 +42,38 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
4042 // 'raw prior',
4143 // prior?.entries.map(entry => entry.name),
4244 // )
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+ )
5970 }
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+ ) {
6177 // const { textSpan } = proxy.getSmartSelectionRange(fileName, position)
6278 // let existing = scriptSnapshot.getText(textSpan.start, textSpan.start + textSpan.length)
6379 // if (existing.includes('\n')) existing = ''
@@ -72,18 +88,47 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
7288 // isSnippet: true,
7389 // })
7490 // } 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+ }
85131 }
86- // }
87132 }
88133 }
89134 }
@@ -107,7 +152,9 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
107152 prior . entries = prior . entries . map ( entry => {
108153 if ( ! standardProps . includes ( entry . name ) ) {
109154 const newName = `☆${ entry . name } `
110- prevCompletionsMap [ newName ] = entry . name
155+ prevCompletionsMap [ newName ] = {
156+ originalName : entry . name ,
157+ }
111158 return {
112159 ...entry ,
113160 insertText : entry . insertText ?? entry . name ,
@@ -176,7 +223,7 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
176223 const prior = info . languageService . getCompletionEntryDetails (
177224 fileName ,
178225 position ,
179- prevCompletionsMap [ entryName ] || entryName ,
226+ prevCompletionsMap [ entryName ] ?. originalName || entryName ,
180227 formatOptions ,
181228 source ,
182229 preferences ,
0 commit comments