11import * as vscode from 'vscode'
2+ import { compact } from '@zardoy/utils'
23import { getExtensionSetting , registerExtensionCommand } from 'vscode-framework'
34import { EmmetResult } from '../typescript/src/ipcTypes'
45import { sendCommand } from './sendCommand'
@@ -15,6 +16,7 @@ export const registerEmmet = async () => {
1516
1617 const emmet = await import ( '@vscode/emmet-helper' )
1718 const reactLangs = [ 'javascriptreact' , 'typescriptreact' ]
19+ let lastStartOffset : number | undefined
1820 vscode . languages . registerCompletionItemProvider (
1921 reactLangs ,
2022 {
@@ -23,10 +25,19 @@ export const registerEmmet = async () => {
2325 const emmetConfig = vscode . workspace . getConfiguration ( 'emmet' )
2426 if ( isEmmetEnabled && ! emmetConfig . excludeLanguages . includes ( document . languageId ) ) return
2527
26- const result = await sendCommand < EmmetResult > ( 'emmet-completions' , { document, position } )
27- if ( ! result ) return
28- const offset : number = document . offsetAt ( position )
29- const sendToEmmet = document . getText ( ) . slice ( offset + result . emmetTextOffset , offset )
28+ const cursorOffset : number = document . offsetAt ( position )
29+
30+ if ( context . triggerKind !== vscode . CompletionTriggerKind . TriggerForIncompleteCompletions || ! lastStartOffset ) {
31+ const result = await sendCommand < EmmetResult > ( 'emmet-completions' , { document, position } )
32+ if ( ! result ) {
33+ lastStartOffset = undefined
34+ return
35+ }
36+
37+ lastStartOffset = cursorOffset + result . emmetTextOffset
38+ }
39+
40+ const sendToEmmet = document . getText ( ) . slice ( lastStartOffset , cursorOffset )
3041 const emmetCompletions = emmet . doComplete (
3142 {
3243 getText : ( ) => sendToEmmet ,
@@ -52,14 +63,16 @@ export const registerEmmet = async () => {
5263 } )
5364 return {
5465 items :
55- improveEmmetCompletions < any > ( normalizedCompletions ) ?. map ( ( { label, insertText, rangeLength, documentation, sortText } ) => ( {
56- label : { label, description : 'EMMET' } ,
57- // sortText is overrided if its a number
58- sortText : Number . isNaN ( + sortText ) ? '075' : sortText ,
59- insertText : new vscode . SnippetString ( insertText ) ,
60- range : new vscode . Range ( position . translate ( 0 , - rangeLength ) , position ) ,
61- documentation : documentation as string ,
62- } ) ) ?? [ ] ,
66+ improveEmmetCompletions < any > ( normalizedCompletions , sendToEmmet ) ?. map (
67+ ( { label, insertText, rangeLength, documentation, sortText } ) => ( {
68+ label : { label, description : 'EMMET' } ,
69+ // sortText is overrided if its a number
70+ sortText : Number . isNaN ( + sortText ) ? '075' : sortText ,
71+ insertText : new vscode . SnippetString ( insertText ) ,
72+ range : new vscode . Range ( position . translate ( 0 , - rangeLength ) , position ) ,
73+ documentation : documentation as string ,
74+ } ) ,
75+ ) ?? [ ] ,
6376 isIncomplete : true ,
6477 }
6578 } ,
@@ -115,27 +128,34 @@ function getEmmetConfiguration() {
115128 }
116129}
117130
118- const improveEmmetCompletions = < T extends Record < 'label' | 'insertText' | 'sortText' , string > > ( items : T [ ] | undefined ) => {
131+ const improveEmmetCompletions = < T extends Record < 'label' | 'insertText' | 'sortText' , string > > ( items : T [ ] | undefined , sendedText : string ) => {
119132 if ( ! items ) return
120133 // TODO-low make to tw= by default when twin.macro is installed?
121134 const dotSnippetOverride = getExtensionSetting ( 'jsxEmmet.dotOverride' )
122135 const modernEmmet = getExtensionSetting ( 'jsxEmmet.modernize' )
123136
124- return items . map ( item => {
125- const { label } = item
126- if ( label === '.' && typeof dotSnippetOverride === 'string' ) item . insertText = dotSnippetOverride
127- // change sorting to most used
128- if ( [ 'div' , 'b' ] . includes ( label ) ) item . sortText = '070'
129- if ( label . startsWith ( 'btn' ) ) item . sortText = '073'
130- if ( modernEmmet ) {
131- // remove id from input suggestions
132- if ( label === 'inp' || label . startsWith ( 'input:password' ) ) {
133- item . insertText = item . insertText . replace ( / i d = " \$ { \d } " / , '' )
134- }
137+ return compact (
138+ items . map ( item => {
139+ const { label } = item
140+ if ( label === '.' && typeof dotSnippetOverride === 'string' ) item . insertText = dotSnippetOverride
141+ // change sorting to most used
142+ if ( [ 'div' , 'b' ] . includes ( label ) ) item . sortText = '070'
143+ if ( label . startsWith ( 'btn' ) ) item . sortText = '073'
144+ if ( modernEmmet ) {
145+ // note that it still allows to use Item* pattern
146+ if ( sendedText [ 0 ] && ! sendedText . startsWith ( sendedText [ 0 ] . toLowerCase ( ) ) && item . insertText === `<${ sendedText } >\${0}</${ sendedText } >` ) {
147+ return undefined
148+ }
135149
136- if ( label === 'textarea' ) item . insertText = `<textarea>$1</textarea>`
137- }
150+ // remove id from input suggestions
151+ if ( label === 'inp' || label . startsWith ( 'input:password' ) ) {
152+ item . insertText = item . insertText . replace ( / i d = " \$ { \d } " / , '' )
153+ }
154+
155+ if ( label === 'textarea' ) item . insertText = `<textarea>$1</textarea>`
156+ }
138157
139- return item
140- } )
158+ return item
159+ } ) ,
160+ )
141161}
0 commit comments