2020-10-08 15:20:54 +00:00
import { State } from './util/state'
import type {
2020-04-11 21:20:45 +00:00
CompletionItem ,
CompletionItemKind ,
Range ,
MarkupKind ,
CompletionList ,
2020-10-08 15:20:54 +00:00
TextDocument ,
Position ,
2020-04-11 21:20:45 +00:00
} from 'vscode-languageserver'
const dlv = require ( 'dlv' )
2020-10-08 15:20:54 +00:00
import removeMeta from './util/removeMeta'
import { getColor , getColorFromValue } from './util/color'
import { isHtmlContext } from './util/html'
import { isCssContext } from './util/css'
import { findLast } from './util/find'
import { stringifyConfigValue , stringifyCss } from './util/stringify'
import { stringifyScreen , Screen } from './util/screens'
import isObject from './util/isObject'
2020-05-03 14:57:15 +00:00
import * as emmetHelper from 'vscode-emmet-helper-bundled'
2020-10-08 15:20:54 +00:00
import { isValidLocationForEmmetAbbreviation } from './util/isValidLocationForEmmetAbbreviation'
import { getDocumentSettings } from './util/getDocumentSettings'
import { isJsContext } from './util/js'
import { naturalExpand } from './util/naturalExpand'
2020-04-27 23:06:47 +00:00
import semver from 'semver'
2020-10-08 15:20:54 +00:00
import { docsUrl } from './util/docsUrl'
import { ensureArray } from './util/array'
2020-05-16 18:10:17 +00:00
import {
getClassAttributeLexer ,
getComputedClassAttributeLexer ,
2020-10-08 15:20:54 +00:00
} from './util/lexers'
import { validateApply } from './util/validateApply'
import { flagEnabled } from './util/flagEnabled'
2020-04-11 21:20:45 +00:00
function completionsFromClassList (
state : State ,
classList : string ,
2020-05-10 12:06:22 +00:00
classListRange : Range ,
filter ? : ( item : CompletionItem ) = > boolean
2020-04-11 21:20:45 +00:00
) : CompletionList {
let classNames = classList . split ( /[\s+]/ )
const partialClassName = classNames [ classNames . length - 1 ]
2020-04-27 21:23:22 +00:00
let sep = state . separator
2020-04-11 21:20:45 +00:00
let parts = partialClassName . split ( sep )
let subset : any
2020-04-13 00:44:43 +00:00
let subsetKey : string [ ] = [ ]
2020-04-11 21:20:45 +00:00
let isSubset : boolean = false
let replacementRange = {
. . . classListRange ,
start : {
. . . classListRange . start ,
character : classListRange.end.character - partialClassName . length ,
} ,
}
for ( let i = parts . length - 1 ; i > 0 ; i -- ) {
let keys = parts . slice ( 0 , i ) . filter ( Boolean )
subset = dlv ( state . classNames . classNames , keys )
2020-11-19 17:34:59 +00:00
if (
typeof subset !== 'undefined' &&
typeof dlv ( subset , [ '__info' , '__rule' ] ) === 'undefined'
) {
2020-04-11 21:20:45 +00:00
isSubset = true
2020-04-13 00:44:43 +00:00
subsetKey = keys
2020-04-11 21:20:45 +00:00
replacementRange = {
. . . replacementRange ,
start : {
. . . replacementRange . start ,
character :
replacementRange . start . character +
keys . join ( sep ) . length +
sep . length ,
} ,
}
break
}
}
2020-11-19 17:34:59 +00:00
// console.log(Object.keys(isSubset ? subset : state.classNames.classNames))
2020-04-11 21:20:45 +00:00
return {
isIncomplete : false ,
2020-05-10 12:06:22 +00:00
items : Object.keys ( isSubset ? subset : state.classNames.classNames )
2020-11-19 17:34:59 +00:00
. filter ( ( k ) = > k !== '__info' )
. filter ( ( className ) = > isContextItem ( state , [ . . . subsetKey , className ] ) )
. map (
( className , index ) : CompletionItem = > {
return {
label : className + sep ,
kind : 9 ,
documentation : null ,
command : {
title : '' ,
command : 'editor.action.triggerSuggest' ,
} ,
sortText : '-' + naturalExpand ( index ) ,
data : [ . . . subsetKey , className ] ,
textEdit : {
newText : className + sep ,
range : replacementRange ,
} ,
2020-04-11 21:20:45 +00:00
}
}
2020-11-19 17:34:59 +00:00
)
. concat (
Object . keys ( isSubset ? subset : state.classNames.classNames )
. filter ( ( className ) = >
dlv ( state . classNames . classNames , [
. . . subsetKey ,
className ,
'__info' ,
] )
)
. map ( ( className , index ) = > {
let kind : CompletionItemKind = 21
let documentation : string = null
const color = getColor ( state , [ className ] )
if ( color !== null ) {
kind = 16
if ( typeof color !== 'string' && color . a !== 0 ) {
documentation = color . toRgbString ( )
}
}
2020-04-11 21:20:45 +00:00
2020-11-19 17:34:59 +00:00
return {
label : className ,
kind ,
documentation ,
sortText : naturalExpand ( index ) ,
data : [ . . . subsetKey , className ] ,
textEdit : {
newText : className ,
range : replacementRange ,
} ,
}
} )
)
. filter ( ( item ) = > {
if ( item === null ) {
return false
2020-04-11 21:20:45 +00:00
}
2020-05-10 12:06:22 +00:00
if ( filter && ! filter ( item ) ) {
2020-11-19 17:34:59 +00:00
return false
2020-05-10 12:06:22 +00:00
}
2020-11-19 17:34:59 +00:00
return true
} ) ,
2020-04-11 21:20:45 +00:00
}
}
function provideClassAttributeCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-11 21:20:45 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
let str = document . getText ( {
2020-04-11 21:20:45 +00:00
start : { line : Math.max ( position . line - 10 , 0 ) , character : 0 } ,
end : position ,
} )
2020-06-12 11:29:04 +00:00
const match = findLast ( /(?:\b|:)class(?:Name)?=['"`{]/gi , str )
2020-04-11 21:20:45 +00:00
if ( match === null ) {
return null
}
2020-05-16 18:10:17 +00:00
const lexer =
match [ 0 ] [ 0 ] === ':'
? getComputedClassAttributeLexer ( )
: getClassAttributeLexer ( )
lexer . reset ( str . substr ( match . index + match [ 0 ] . length - 1 ) )
try {
let tokens = Array . from ( lexer )
let last = tokens [ tokens . length - 1 ]
if ( last . type . startsWith ( 'start' ) || last . type === 'classlist' ) {
let classList = ''
for ( let i = tokens . length - 1 ; i >= 0 ; i -- ) {
if ( tokens [ i ] . type === 'classlist' ) {
classList = tokens [ i ] . value + classList
} else {
break
}
}
2020-04-11 21:20:45 +00:00
return completionsFromClassList ( state , classList , {
start : {
line : position.line ,
character : position.character - classList . length ,
} ,
end : position ,
} )
}
2020-05-16 18:10:17 +00:00
} catch ( _ ) { }
2020-04-11 21:20:45 +00:00
2020-05-16 18:10:17 +00:00
return null
2020-04-11 21:20:45 +00:00
}
function provideAtApplyCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-11 21:20:45 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
let str = document . getText ( {
2020-04-11 21:20:45 +00:00
start : { line : Math.max ( position . line - 30 , 0 ) , character : 0 } ,
end : position ,
} )
const match = findLast ( /@apply\s+(?<classList>[^;}]*)$/gi , str )
if ( match === null ) {
return null
}
const classList = match . groups . classList
2020-05-10 12:06:22 +00:00
return completionsFromClassList (
state ,
classList ,
{
start : {
line : position.line ,
character : position.character - classList . length ,
} ,
end : position ,
2020-04-11 21:20:45 +00:00
} ,
2020-05-10 12:06:22 +00:00
( item ) = > {
2020-10-08 15:20:54 +00:00
if ( item . kind === 9 ) {
2020-11-19 17:34:59 +00:00
return (
semver . gte ( state . version , '2.0.0-alpha.1' ) ||
flagEnabled ( state , 'applyComplexClasses' )
)
2020-08-21 14:30:29 +00:00
}
let validated = validateApply ( state , item . data )
return validated !== null && validated . isApplyable === true
2020-05-10 12:06:22 +00:00
}
)
2020-04-11 21:20:45 +00:00
}
function provideClassNameCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-11 21:20:45 +00:00
) : CompletionList {
2020-04-16 21:39:16 +00:00
if (
2020-10-08 15:20:54 +00:00
isHtmlContext ( state , document , position ) ||
isJsContext ( state , document , position )
2020-04-16 21:39:16 +00:00
) {
2020-10-08 15:20:54 +00:00
return provideClassAttributeCompletions ( state , document , position )
2020-04-11 21:20:45 +00:00
}
2020-10-08 15:20:54 +00:00
if ( isCssContext ( state , document , position ) ) {
return provideAtApplyCompletions ( state , document , position )
2020-04-11 21:20:45 +00:00
}
return null
}
function provideCssHelperCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-11 21:20:45 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
if ( ! isCssContext ( state , document , position ) ) {
2020-04-11 21:20:45 +00:00
return null
}
2020-10-08 15:20:54 +00:00
let text = document . getText ( {
2020-04-11 21:20:45 +00:00
start : { line : position.line , character : 0 } ,
// read one extra character so we can see if it's a ] later
end : { line : position.line , character : position.character + 1 } ,
} )
const match = text
. substr ( 0 , text . length - 1 ) // don't include that extra character from earlier
. match ( /\b(?<helper>config|theme)\(['"](?<keys>[^'"]*)$/ )
if ( match === null ) {
return null
}
let base =
match . groups . helper === 'config'
? state . config
: dlv ( state . config , 'theme' , { } )
let parts = match . groups . keys . split ( /([\[\].]+)/ )
let keys = parts . filter ( ( _ , i ) = > i % 2 === 0 )
let separators = parts . filter ( ( _ , i ) = > i % 2 !== 0 )
// let obj =
// keys.length === 1 ? base : dlv(base, keys.slice(0, keys.length - 1), {})
// if (!isObject(obj)) return null
function totalLength ( arr : string [ ] ) : number {
return arr . reduce ( ( acc , cur ) = > acc + cur . length , 0 )
}
let obj : any
let offset : number = 0
let separator : string = separators . length
? separators [ separators . length - 1 ]
: null
if ( keys . length === 1 ) {
obj = base
} else {
for ( let i = keys . length - 1 ; i > 0 ; i -- ) {
let o = dlv ( base , keys . slice ( 0 , i ) )
if ( isObject ( o ) ) {
obj = o
offset = totalLength ( parts . slice ( i * 2 ) )
separator = separators [ i - 1 ]
break
}
}
}
if ( ! obj ) return null
return {
isIncomplete : false ,
2020-04-27 22:18:28 +00:00
items : Object.keys ( obj ) . map ( ( item , index ) = > {
2020-05-03 19:52:14 +00:00
let color = getColorFromValue ( obj [ item ] )
2020-04-11 21:20:45 +00:00
const replaceDot : boolean =
item . indexOf ( '.' ) !== - 1 && separator && separator . endsWith ( '.' )
const insertClosingBrace : boolean =
text . charAt ( text . length - 1 ) !== ']' &&
( replaceDot || ( separator && separator . endsWith ( '[' ) ) )
2020-04-27 23:30:32 +00:00
const detail = stringifyConfigValue ( obj [ item ] )
2020-04-11 21:20:45 +00:00
return {
label : item ,
filterText : ` ${ replaceDot ? '.' : '' } ${ item } ` ,
2020-04-27 22:18:28 +00:00
sortText : naturalExpand ( index ) ,
2020-10-08 15:20:54 +00:00
kind : color ? 16 : isObject ( obj [ item ] ) ? 9 : 10 ,
2020-04-27 23:30:32 +00:00
// VS Code bug causes '0' to not display in some cases
detail : detail === '0' ? '0 ' : detail ,
2020-04-11 21:20:45 +00:00
documentation : color ,
textEdit : {
newText : ` ${ replaceDot ? '[' : '' } ${ item } ${
insertClosingBrace ? ']' : ''
} ` ,
range : {
start : {
line : position.line ,
character :
position . character -
keys [ keys . length - 1 ] . length -
( replaceDot ? 1 : 0 ) -
offset ,
} ,
end : position ,
} ,
} ,
data : 'helper' ,
}
} ) ,
}
}
2020-04-27 23:06:47 +00:00
// TODO: vary docs links based on Tailwind version
2020-04-27 22:52:31 +00:00
function provideTailwindDirectiveCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-27 22:52:31 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
if ( ! isCssContext ( state , document , position ) ) {
2020-04-27 22:52:31 +00:00
return null
}
2020-10-08 15:20:54 +00:00
let text = document . getText ( {
2020-04-27 22:52:31 +00:00
start : { line : position.line , character : 0 } ,
end : position ,
} )
const match = text . match ( /^\s*@tailwind\s+(?<partial>[^\s]*)$/i )
if ( match === null ) return null
return {
isIncomplete : false ,
items : [
2020-04-27 23:06:47 +00:00
semver . gte ( state . version , '1.0.0-beta.1' )
? {
label : 'base' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` This injects Tailwind’ s base styles and any base styles registered by plugins. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
2020-04-27 23:06:47 +00:00
} ,
}
: {
label : 'preflight' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` This injects Tailwind’ s base styles, which is a combination of Normalize.css and some additional base styles. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
2020-04-27 23:06:47 +00:00
} ,
} ,
2020-04-27 22:52:31 +00:00
{
label : 'components' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` This injects Tailwind’ s component classes and any component classes registered by plugins. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
2020-04-27 22:52:31 +00:00
} ,
} ,
{
label : 'utilities' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` This injects Tailwind’ s utility classes and any utility classes registered by plugins. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
2020-04-27 22:52:31 +00:00
} ,
} ,
{
label : 'screens' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` Use this directive to control where Tailwind injects the responsive variations of each utility. \ n \ nIf omitted, Tailwind will append these classes to the very end of your stylesheet by default. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
2020-04-27 22:52:31 +00:00
} ,
} ,
] . map ( ( item ) = > ( {
. . . item ,
2020-10-08 15:20:54 +00:00
kind : 21 ,
2020-04-27 23:07:07 +00:00
data : '@tailwind' ,
2020-04-27 22:52:31 +00:00
textEdit : {
newText : item.label ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length ,
} ,
end : position ,
} ,
} ,
} ) ) ,
}
}
2020-04-12 22:48:57 +00:00
function provideVariantsDirectiveCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-12 22:48:57 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
if ( ! isCssContext ( state , document , position ) ) {
2020-04-12 22:48:57 +00:00
return null
}
2020-10-08 15:20:54 +00:00
let text = document . getText ( {
2020-04-12 22:48:57 +00:00
start : { line : position.line , character : 0 } ,
end : position ,
} )
const match = text . match ( /^\s*@variants\s+(?<partial>[^}]*)$/i )
if ( match === null ) return null
const parts = match . groups . partial . split ( /\s*,\s*/ )
if ( /\s+/ . test ( parts [ parts . length - 1 ] ) ) return null
const existingVariants = parts . slice ( 0 , parts . length - 1 )
return {
isIncomplete : false ,
2020-04-16 21:44:52 +00:00
items : state.variants
2020-04-12 22:48:57 +00:00
. filter ( ( v ) = > existingVariants . indexOf ( v ) === - 1 )
. map ( ( variant ) = > ( {
// TODO: detail
label : variant ,
2020-10-08 15:20:54 +00:00
kind : 21 ,
2020-04-12 22:48:57 +00:00
data : 'variant' ,
textEdit : {
newText : variant ,
range : {
start : {
line : position.line ,
character : position.character - parts [ parts . length - 1 ] . length ,
} ,
end : position ,
} ,
} ,
} ) ) ,
}
}
2020-04-12 17:11:41 +00:00
function provideScreenDirectiveCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-12 17:11:41 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
if ( ! isCssContext ( state , document , position ) ) {
2020-04-12 17:11:41 +00:00
return null
}
2020-10-08 15:20:54 +00:00
let text = document . getText ( {
2020-04-12 17:11:41 +00:00
start : { line : position.line , character : 0 } ,
end : position ,
} )
const match = text . match ( /^\s*@screen\s+(?<partial>[^\s]*)$/i )
if ( match === null ) return null
const screens = dlv (
state . config ,
[ 'screens' ] ,
dlv ( state . config , [ 'theme' , 'screens' ] , { } )
)
if ( ! isObject ( screens ) ) return null
return {
isIncomplete : false ,
2020-04-27 22:18:28 +00:00
items : Object.keys ( screens ) . map ( ( screen , index ) = > ( {
2020-04-12 17:11:41 +00:00
label : screen ,
2020-10-08 15:20:54 +00:00
kind : 21 ,
2020-04-27 21:48:30 +00:00
data : 'screen' ,
2020-04-27 22:18:28 +00:00
sortText : naturalExpand ( index ) ,
2020-04-12 17:11:41 +00:00
textEdit : {
newText : screen ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length ,
} ,
end : position ,
} ,
} ,
} ) ) ,
}
}
2020-04-12 16:55:32 +00:00
function provideCssDirectiveCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-12 16:55:32 +00:00
) : CompletionList {
2020-10-08 15:20:54 +00:00
if ( ! isCssContext ( state , document , position ) ) {
2020-04-12 16:55:32 +00:00
return null
}
2020-10-08 15:20:54 +00:00
let text = document . getText ( {
2020-04-12 16:55:32 +00:00
start : { line : position.line , character : 0 } ,
end : position ,
} )
const match = text . match ( /^\s*@(?<partial>[a-z]*)$/i )
if ( match === null ) return null
const items : CompletionItem [ ] = [
{
label : '@tailwind' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` Use the \` @tailwind \` directive to insert Tailwind’ s \` base \` , \` components \` , \` utilities \` and \` screens \` styles into your CSS. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
2020-04-12 16:55:32 +00:00
} ,
} ,
{
label : '@variants' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` You can generate \` responsive \` , \` hover \` , \` focus \` , \` active \` , and \` group-hover \` versions of your own utilities by wrapping their definitions in the \` @variants \` directive. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#variants'
) } ) ` ,
2020-04-12 16:55:32 +00:00
} ,
} ,
{
label : '@responsive' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` You can generate responsive variants of your own classes by wrapping their definitions in the \` @responsive \` directive. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#responsive'
) } ) ` ,
2020-04-12 16:55:32 +00:00
} ,
} ,
{
label : '@screen' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` The \` @screen \` directive allows you to create media queries that reference your breakpoints by name instead of duplicating their values in your own CSS. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#screen'
) } ) ` ,
2020-04-12 16:55:32 +00:00
} ,
} ,
{
label : '@apply' ,
documentation : {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-28 20:42:43 +00:00
value : ` Use \` @apply \` to inline any existing utility classes into your own custom CSS. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#apply'
) } ) ` ,
2020-04-12 16:55:32 +00:00
} ,
} ,
]
return {
isIncomplete : false ,
items : items.map ( ( item ) = > ( {
. . . item ,
2020-10-08 15:20:54 +00:00
kind : 14 ,
2020-04-12 16:55:32 +00:00
data : 'directive' ,
textEdit : {
newText : item.label ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length - 1 ,
} ,
end : position ,
} ,
} ,
} ) ) ,
}
}
2020-04-16 21:39:16 +00:00
async function provideEmmetCompletions (
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
2020-04-16 21:39:16 +00:00
) : Promise < CompletionList > {
2020-10-08 15:20:54 +00:00
let settings = await getDocumentSettings ( state , document )
2020-06-15 13:31:04 +00:00
if ( settings . emmetCompletions !== true ) return null
2020-10-08 15:20:54 +00:00
const syntax = isHtmlContext ( state , document , position )
2020-04-16 21:39:16 +00:00
? 'html'
2020-10-08 15:20:54 +00:00
: isJsContext ( state , document , position )
2020-04-16 21:39:16 +00:00
? 'jsx'
: null
if ( syntax === null ) {
return null
}
const extractAbbreviationResults = emmetHelper . extractAbbreviation (
2020-10-08 15:20:54 +00:00
document ,
2020-04-16 21:39:16 +00:00
position ,
true
)
if (
! extractAbbreviationResults ||
! emmetHelper . isAbbreviationValid (
syntax ,
extractAbbreviationResults . abbreviation
)
) {
return null
}
if (
! isValidLocationForEmmetAbbreviation (
2020-10-08 15:20:54 +00:00
document ,
2020-04-16 21:39:16 +00:00
extractAbbreviationResults . abbreviationRange
)
) {
return null
}
2020-10-08 15:20:54 +00:00
const emmetItems = emmetHelper . doComplete ( document , position , syntax , { } )
2020-04-16 21:39:16 +00:00
if ( ! emmetItems || ! emmetItems . items || emmetItems . items . length !== 1 ) {
return null
}
// https://github.com/microsoft/vscode/issues/86941
if ( emmetItems . items [ 0 ] . label === 'widows: ;' ) {
return null
}
const parts = emmetItems . items [ 0 ] . label . split ( '.' )
if ( parts . length < 2 ) return null
return completionsFromClassList ( state , parts [ parts . length - 1 ] , {
start : {
line : position.line ,
character : position.character - parts [ parts . length - 1 ] . length ,
} ,
end : position ,
} )
}
2020-10-08 15:20:54 +00:00
export async function doComplete (
2020-04-11 21:20:45 +00:00
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
position : Position
) {
2020-04-11 21:20:45 +00:00
if ( state === null ) return { items : [ ] , isIncomplete : false }
2020-04-16 21:39:16 +00:00
const result =
2020-10-08 15:20:54 +00:00
provideClassNameCompletions ( state , document , position ) ||
provideCssHelperCompletions ( state , document , position ) ||
provideCssDirectiveCompletions ( state , document , position ) ||
provideScreenDirectiveCompletions ( state , document , position ) ||
provideVariantsDirectiveCompletions ( state , document , position ) ||
provideTailwindDirectiveCompletions ( state , document , position )
2020-04-16 21:39:16 +00:00
if ( result ) return result
2020-10-08 15:20:54 +00:00
return provideEmmetCompletions ( state , document , position )
2020-04-11 21:20:45 +00:00
}
export function resolveCompletionItem (
state : State ,
item : CompletionItem
) : CompletionItem {
2020-04-27 23:07:07 +00:00
if ( [ 'helper' , 'directive' , 'variant' , '@tailwind' ] . includes ( item . data ) ) {
2020-04-27 21:48:30 +00:00
return item
}
if ( item . data === 'screen' ) {
let screens = dlv (
state . config ,
[ 'theme' , 'screens' ] ,
dlv ( state . config , [ 'screens' ] , { } )
)
if ( ! isObject ( screens ) ) screens = { }
item . detail = stringifyScreen ( screens [ item . label ] as Screen )
2020-04-11 21:20:45 +00:00
return item
}
2020-11-19 17:34:59 +00:00
const className = dlv ( state . classNames . classNames , [ . . . item . data , '__info' ] )
if ( item . kind === 9 ) {
2020-04-13 00:44:43 +00:00
item . detail = state . classNames . context [
item . data [ item . data . length - 1 ]
] . join ( ', ' )
2020-04-11 21:20:45 +00:00
} else {
item . detail = getCssDetail ( state , className )
if ( ! item . documentation ) {
2020-04-13 00:44:43 +00:00
const css = stringifyCss ( item . data . join ( ':' ) , className )
if ( css ) {
item . documentation = {
2020-10-08 15:20:54 +00:00
kind : 'markdown' as typeof MarkupKind . Markdown ,
2020-04-13 00:44:43 +00:00
value : [ '```css' , css , '```' ] . join ( '\n' ) ,
}
2020-04-11 21:20:45 +00:00
}
}
}
return item
}
function isContextItem ( state : State , keys : string [ ] ) : boolean {
2020-11-19 17:34:59 +00:00
const item = dlv ( state . classNames . classNames , [ keys ] )
if ( ! isObject ( item ) ) {
return false
}
if ( ! state . classNames . context [ keys [ keys . length - 1 ] ] ) {
return false
}
if ( Object . keys ( item ) . filter ( ( x ) = > x !== '__info' ) . length > 0 ) {
return true
}
return isObject ( item . __info ) && ! item . __info . __rule
2020-04-11 21:20:45 +00:00
}
function stringifyDecls ( obj : any ) : string {
2020-05-02 12:18:30 +00:00
let props = Object . keys ( obj )
let nonCustomProps = props . filter ( ( prop ) = > ! prop . startsWith ( '--' ) )
if ( props . length !== nonCustomProps . length && nonCustomProps . length !== 0 ) {
props = nonCustomProps
}
return props
. map ( ( prop ) = >
ensureArray ( obj [ prop ] )
. map ( ( value ) = > ` ${ prop } : ${ value } ; ` )
. join ( ' ' )
)
2020-04-11 21:20:45 +00:00
. join ( ' ' )
}
function getCssDetail ( state : State , className : any ) : string {
if ( Array . isArray ( className ) ) {
return ` ${ className . length } rules `
}
2020-04-13 00:44:43 +00:00
if ( className . __rule === true ) {
return stringifyDecls ( removeMeta ( className ) )
2020-04-11 21:20:45 +00:00
}
2020-04-13 00:44:43 +00:00
return null
2020-04-11 21:20:45 +00:00
}