2020-04-11 21:20:45 +00:00
import { State } from '../util/state'
import {
CompletionItem ,
CompletionItemKind ,
CompletionParams ,
Range ,
MarkupKind ,
CompletionList ,
} from 'vscode-languageserver'
const dlv = require ( 'dlv' )
import removeMeta from '../util/removeMeta'
2020-05-03 19:52:14 +00:00
import { getColor , getColorFromValue } from '../util/color'
2020-04-11 22:34:03 +00:00
import { isHtmlContext } from '../util/html'
import { isCssContext } from '../util/css'
2020-04-11 21:20:45 +00:00
import { findLast , findJsxStrings , arrFindLast } from '../util/find'
2020-04-12 14:48:38 +00:00
import { stringifyConfigValue , stringifyCss } from '../util/stringify'
2020-04-27 21:48:30 +00:00
import { stringifyScreen , Screen } from '../util/screens'
2020-05-03 16:10:38 +00:00
import isObject from '../../util/isObject'
2020-05-03 14:57:15 +00:00
import * as emmetHelper from 'vscode-emmet-helper-bundled'
2020-04-16 21:39:16 +00:00
import { isValidLocationForEmmetAbbreviation } from '../util/isValidLocationForEmmetAbbreviation'
import { getDocumentSettings } from '../util/getDocumentSettings'
import { isJsContext } from '../util/js'
2020-04-27 22:18:28 +00:00
import { naturalExpand } from '../util/naturalExpand'
2020-04-27 23:06:47 +00:00
import semver from 'semver'
2020-04-28 20:42:43 +00:00
import { docsUrl } from '../util/docsUrl'
2020-05-03 16:10:38 +00:00
import { ensureArray } from '../../util/array'
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 )
if ( typeof subset !== 'undefined' && typeof subset . __rule === 'undefined' ) {
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
}
}
return {
isIncomplete : false ,
2020-05-10 12:06:22 +00:00
items : Object.keys ( isSubset ? subset : state.classNames.classNames )
. map ( ( className , index ) = > {
2020-04-27 21:14:47 +00:00
let label = className
2020-04-11 21:20:45 +00:00
let kind : CompletionItemKind = CompletionItemKind . Constant
let documentation : string = null
2020-04-27 21:14:47 +00:00
let command : any
2020-04-27 22:18:28 +00:00
let sortText = naturalExpand ( index )
2020-04-13 00:44:43 +00:00
if ( isContextItem ( state , [ . . . subsetKey , className ] ) ) {
2020-04-11 21:20:45 +00:00
kind = CompletionItemKind . Module
2020-04-27 21:14:47 +00:00
command = { title : '' , command : 'editor.action.triggerSuggest' }
2020-04-27 21:23:22 +00:00
label += sep
2020-04-27 22:18:28 +00:00
sortText = '-' + sortText // move to top
2020-04-11 21:20:45 +00:00
} else {
const color = getColor ( state , [ className ] )
if ( color ) {
kind = CompletionItemKind . Color
2020-04-29 22:46:05 +00:00
documentation = color . documentation
2020-04-11 21:20:45 +00:00
}
}
2020-05-10 12:06:22 +00:00
const item = {
2020-04-27 21:14:47 +00:00
label ,
2020-04-11 21:20:45 +00:00
kind ,
documentation ,
2020-04-27 21:14:47 +00:00
command ,
2020-04-27 22:18:28 +00:00
sortText ,
2020-04-13 00:44:43 +00:00
data : [ . . . subsetKey , className ] ,
2020-04-11 21:20:45 +00:00
textEdit : {
2020-04-27 21:14:47 +00:00
newText : label ,
2020-04-11 21:20:45 +00:00
range : replacementRange ,
} ,
}
2020-05-10 12:06:22 +00:00
if ( filter && ! filter ( item ) ) {
return null
}
return item
} )
. filter ( ( item ) = > item !== null ) ,
2020-04-11 21:20:45 +00:00
}
}
function provideClassAttributeCompletions (
state : State ,
{ context , position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
let str = doc . getText ( {
start : { line : Math.max ( position . line - 10 , 0 ) , character : 0 } ,
end : position ,
} )
const match = findLast ( /\bclass(?:Name)?=(?<initial>['"`{])/gi , str )
if ( match === null ) {
return null
}
const rest = str . substr ( match . index + match [ 0 ] . length )
if ( match . groups . initial === '{' ) {
const strings = findJsxStrings ( '{' + rest )
const lastOpenString = arrFindLast (
strings ,
( string ) = > typeof string . end === 'undefined'
)
if ( lastOpenString ) {
const classList = str . substr (
str . length - rest . length + lastOpenString . start - 1
)
return completionsFromClassList ( state , classList , {
start : {
line : position.line ,
character : position.character - classList . length ,
} ,
end : position ,
} )
}
return null
}
if ( rest . indexOf ( match . groups . initial ) !== - 1 ) {
return null
}
return completionsFromClassList ( state , rest , {
start : {
line : position.line ,
character : position.character - rest . length ,
} ,
end : position ,
} )
}
function provideAtApplyCompletions (
state : State ,
{ context , position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
let str = doc . getText ( {
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 ) = > {
// TODO: first line excludes all subtrees but there could _technically_ be
// valid apply-able class names in there. Will be correct in 99% of cases
if ( item . kind === CompletionItemKind . Module ) return false
let info = dlv ( state . classNames . classNames , item . data )
return (
! Array . isArray ( info ) &&
info . __source === 'utilities' &&
( info . __context || [ ] ) . length === 0 &&
( info . __pseudo || [ ] ) . length === 0
)
}
)
2020-04-11 21:20:45 +00:00
}
function provideClassNameCompletions (
state : State ,
params : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( params . textDocument . uri )
2020-04-16 21:39:16 +00:00
if (
2020-05-03 17:11:45 +00:00
isHtmlContext ( state , doc , params . position ) ||
isJsContext ( state , doc , params . position )
2020-04-16 21:39:16 +00:00
) {
2020-04-11 21:20:45 +00:00
return provideClassAttributeCompletions ( state , params )
}
2020-05-03 17:11:45 +00:00
if ( isCssContext ( state , doc , params . position ) ) {
2020-04-11 21:20:45 +00:00
return provideAtApplyCompletions ( state , params )
}
return null
}
function provideCssHelperCompletions (
state : State ,
{ position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
2020-05-03 17:11:45 +00:00
if ( ! isCssContext ( state , doc , position ) ) {
2020-04-11 21:20:45 +00:00
return null
}
let text = doc . getText ( {
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-04-11 21:20:45 +00:00
kind : color
? CompletionItemKind . Color
: isObject ( obj [ item ] )
? CompletionItemKind . Module
: CompletionItemKind . Property ,
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 ,
{ position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
2020-05-03 17:11:45 +00:00
if ( ! isCssContext ( state , doc , position ) ) {
2020-04-27 22:52:31 +00:00
return null
}
let text = doc . getText ( {
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 : {
kind : 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 : {
kind : 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 : {
kind : 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 : {
kind : 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 : {
kind : 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 ,
kind : CompletionItemKind.Constant ,
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 ,
{ position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
2020-05-03 17:11:45 +00:00
if ( ! isCssContext ( state , doc , position ) ) {
2020-04-12 22:48:57 +00:00
return null
}
let text = doc . getText ( {
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 ,
kind : CompletionItemKind.Constant ,
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 ,
{ position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
2020-05-03 17:11:45 +00:00
if ( ! isCssContext ( state , doc , position ) ) {
2020-04-12 17:11:41 +00:00
return null
}
let text = doc . getText ( {
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 ,
kind : CompletionItemKind.Constant ,
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 ,
{ position , textDocument } : CompletionParams
) : CompletionList {
let doc = state . editor . documents . get ( textDocument . uri )
2020-05-03 17:11:45 +00:00
if ( ! isCssContext ( state , doc , position ) ) {
2020-04-12 16:55:32 +00:00
return null
}
let text = doc . getText ( {
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 : {
kind : 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 : {
kind : 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 : {
kind : 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 : {
kind : 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 : {
kind : 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 ,
kind : CompletionItemKind.Keyword ,
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 ,
{ position , textDocument } : CompletionParams
) : Promise < CompletionList > {
let settings = await getDocumentSettings ( state , textDocument . uri )
if ( settings . emmetCompletions !== true ) return null
let doc = state . editor . documents . get ( textDocument . uri )
2020-05-03 17:11:45 +00:00
const syntax = isHtmlContext ( state , doc , position )
2020-04-16 21:39:16 +00:00
? 'html'
2020-05-03 17:11:45 +00:00
: isJsContext ( state , doc , position )
2020-04-16 21:39:16 +00:00
? 'jsx'
: null
if ( syntax === null ) {
return null
}
const extractAbbreviationResults = emmetHelper . extractAbbreviation (
doc ,
position ,
true
)
if (
! extractAbbreviationResults ||
! emmetHelper . isAbbreviationValid (
syntax ,
extractAbbreviationResults . abbreviation
)
) {
return null
}
if (
! isValidLocationForEmmetAbbreviation (
doc ,
extractAbbreviationResults . abbreviationRange
)
) {
return null
}
const emmetItems = emmetHelper . doComplete ( doc , position , syntax , { } )
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 ,
} )
}
export async function provideCompletions (
2020-04-11 21:20:45 +00:00
state : State ,
params : CompletionParams
2020-04-16 21:39:16 +00:00
) : Promise < CompletionList > {
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-04-11 21:20:45 +00:00
provideClassNameCompletions ( state , params ) ||
2020-04-12 16:55:32 +00:00
provideCssHelperCompletions ( state , params ) ||
2020-04-12 17:11:41 +00:00
provideCssDirectiveCompletions ( state , params ) ||
2020-04-12 22:48:57 +00:00
provideScreenDirectiveCompletions ( state , params ) ||
2020-04-27 22:52:31 +00:00
provideVariantsDirectiveCompletions ( state , params ) ||
provideTailwindDirectiveCompletions ( state , params )
2020-04-16 21:39:16 +00:00
if ( result ) return result
return provideEmmetCompletions ( state , params )
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-04-13 00:44:43 +00:00
const className = dlv ( state . classNames . classNames , item . data )
if ( isContextItem ( state , item . data ) ) {
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 = {
kind : MarkupKind.Markdown ,
value : [ '```css' , css , '```' ] . join ( '\n' ) ,
}
2020-04-11 21:20:45 +00:00
}
}
}
return item
}
function isContextItem ( state : State , keys : string [ ] ) : boolean {
const item = dlv ( state . classNames . classNames , keys )
return Boolean (
2020-04-13 00:44:43 +00:00
isObject ( item ) &&
! item . __rule &&
2020-04-11 21:20:45 +00:00
! Array . isArray ( item ) &&
state . classNames . context [ keys [ keys . length - 1 ] ]
)
}
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
}