2022-10-17 17:07:29 +00:00
import { Settings , State } from './util/state'
2020-10-08 15:20:54 +00:00
import type {
2020-04-11 21:20:45 +00:00
CompletionItem ,
CompletionItemKind ,
Range ,
MarkupKind ,
CompletionList ,
2020-10-08 15:20:54 +00:00
Position ,
2021-05-18 11:22:18 +00:00
CompletionContext ,
2020-04-11 21:20:45 +00:00
} from 'vscode-languageserver'
2023-05-15 10:21:43 +00:00
import type { TextDocument } from 'vscode-languageserver-textdocument'
2022-04-13 16:10:47 +00:00
import dlv from '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'
2021-10-08 15:51:14 +00:00
import { findLast , matchClassAttributes } from './util/find'
2020-10-08 15:20:54 +00:00
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'
2022-03-02 17:16:35 +00:00
import { isJsDoc , isJsxContext } from './util/js'
2020-10-08 15:20:54 +00:00
import { naturalExpand } from './util/naturalExpand'
2022-07-06 15:07:13 +00:00
import * as semver from './util/semver'
2020-10-08 15:20:54 +00:00
import { docsUrl } from './util/docsUrl'
import { ensureArray } from './util/array'
2021-05-03 17:00:04 +00:00
import { getClassAttributeLexer , getComputedClassAttributeLexer } from './util/lexers'
2020-10-08 15:20:54 +00:00
import { validateApply } from './util/validateApply'
import { flagEnabled } from './util/flagEnabled'
2021-05-03 17:00:04 +00:00
import * as jit from './util/jit'
import { getVariantsFromClassName } from './util/getVariantsFromClassName'
2021-10-01 13:11:45 +00:00
import * as culori from 'culori'
2022-08-05 15:58:50 +00:00
import Regex from 'becke-ch--regex--s0-0-v1--base--pl--lib'
2023-05-02 09:44:37 +00:00
import {
addPixelEquivalentsToMediaQuery ,
addPixelEquivalentsToValue ,
} from './util/pixelEquivalents'
2021-05-03 17:00:04 +00:00
let isUtil = ( className ) = >
Array . isArray ( className . __info )
? className . __info . some ( ( x ) = > x . __source === 'utilities' )
: className . __info . __source === 'utilities'
2020-04-11 21:20:45 +00:00
2020-11-26 18:27:13 +00:00
export function completionsFromClassList (
2020-04-11 21:20:45 +00:00
state : State ,
classList : string ,
2020-05-10 12:06:22 +00:00
classListRange : Range ,
2023-05-02 09:44:37 +00:00
rootFontSize : number ,
2021-05-03 17:00:04 +00:00
filter ? : ( item : CompletionItem ) = > boolean ,
2021-05-18 11:22:18 +00:00
context? : CompletionContext
2020-04-11 21:20:45 +00:00
) : CompletionList {
2023-01-27 17:11:37 +00:00
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 ,
2023-01-27 17:11:37 +00:00
start : {
. . . classListRange . start ,
character : classListRange.end.character - partialClassName . length ,
} ,
2020-04-11 21:20:45 +00:00
}
2021-05-03 17:00:04 +00:00
if ( state . jit ) {
2023-01-03 16:22:15 +00:00
let { variants : existingVariants , offset } = getVariantsFromClassName ( state , partialClassName )
2021-05-18 11:22:18 +00:00
if (
context &&
( context . triggerKind === 1 ||
( context . triggerKind === 2 && context . triggerCharacter === '/' ) ) &&
partialClassName . includes ( '/' )
) {
2023-01-03 16:22:15 +00:00
// modifiers
let modifiers : string [ ]
2021-05-18 11:22:18 +00:00
let beforeSlash = partialClassName . split ( '/' ) . slice ( 0 , - 1 ) . join ( '/' )
2023-01-03 16:22:15 +00:00
2023-01-27 10:33:35 +00:00
if ( state . classListContainsMetadata ) {
2023-01-03 16:22:15 +00:00
let baseClassName = beforeSlash . slice ( offset )
modifiers = state . classList . find (
( cls ) = > Array . isArray ( cls ) && cls [ 0 ] === baseClassName
2023-01-27 10:33:35 +00:00
) ? . [ 1 ] ? . modifiers
2023-01-03 16:22:15 +00:00
} else {
let testClass = beforeSlash + '/[0]'
let { rules } = jit . generateRules ( state , [ testClass ] )
if ( rules . length > 0 ) {
let opacities = dlv ( state . config , 'theme.opacity' , { } )
if ( ! isObject ( opacities ) ) {
opacities = { }
}
modifiers = Object . keys ( opacities )
2021-05-18 11:22:18 +00:00
}
2023-01-03 16:22:15 +00:00
}
if ( modifiers ) {
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : modifiers.map ( ( modifier , index ) = > {
let className = ` ${ beforeSlash } / ${ modifier } `
let kind : CompletionItemKind = 21
let documentation : string | undefined
const color = getColor ( state , className )
if ( color !== null ) {
kind = 16
if ( typeof color !== 'string' && ( color . alpha ? ? 1 ) !== 0 ) {
documentation = culori . formatRgb ( color )
}
2021-05-18 11:22:18 +00:00
}
2023-01-27 10:30:27 +00:00
return {
label : className ,
. . . ( documentation ? { documentation } : { } ) ,
kind ,
sortText : naturalExpand ( index ) ,
}
} ) ,
} ,
{
range : replacementRange ,
data : state.completionItemData ,
} ,
state . editor . capabilities . itemDefaults
)
2021-05-18 11:22:18 +00:00
}
}
2021-05-03 17:00:04 +00:00
replacementRange . start . character += offset
let important = partialClassName . substr ( offset ) . startsWith ( '!' )
if ( important ) {
replacementRange . start . character += 1
}
let items : CompletionItem [ ] = [ ]
if ( ! important ) {
2022-10-17 17:05:04 +00:00
let variantOrder = 0
function variantItem (
2023-01-27 10:30:27 +00:00
item : Omit < CompletionItem , ' kind ' | ' data ' | ' command ' | ' sortText ' | ' textEdit ' >
2022-10-17 17:05:04 +00:00
) : CompletionItem {
return {
kind : 9 ,
2023-01-27 10:30:27 +00:00
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'variant' ,
} ,
2022-10-17 17:05:04 +00:00
command :
item . insertTextFormat === 2 // Snippet
? undefined
: {
title : '' ,
command : 'editor.action.triggerSuggest' ,
} ,
sortText : '-' + naturalExpand ( variantOrder ++ ) ,
. . . item ,
}
}
2021-12-06 15:40:54 +00:00
2021-05-03 17:00:04 +00:00
items . push (
2022-10-17 17:05:04 +00:00
. . . state . variants . flatMap ( ( variant ) = > {
let items : CompletionItem [ ] = [ ]
if ( variant . isArbitrary ) {
items . push (
variantItem ( {
label : ` ${ variant . name } ${ variant . hasDash ? '-' : '' } [] ${ sep } ` ,
insertTextFormat : 2 ,
2023-01-27 10:30:27 +00:00
textEditText : ` ${ variant . name } ${ variant . hasDash ? '-' : '' } [ \ ${ 1 } ] ${ sep } \ ${ 0 } ` ,
2022-10-17 17:05:04 +00:00
// command: {
// title: '',
// command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
// arguments: [variant.name, replacementRange],
// },
} )
)
} else if ( ! existingVariants . includes ( variant . name ) ) {
let shouldSortVariants = ! semver . gte ( state . version , '2.99.0' )
let resultingVariants = [ . . . existingVariants , variant . name ]
2021-12-06 15:40:54 +00:00
if ( shouldSortVariants ) {
2022-10-17 17:05:04 +00:00
let allVariants = state . variants . map ( ( { name } ) = > name )
2021-12-06 15:40:54 +00:00
resultingVariants = resultingVariants . sort (
( a , b ) = > allVariants . indexOf ( b ) - allVariants . indexOf ( a )
)
}
2021-05-03 17:00:04 +00:00
2022-10-17 17:05:04 +00:00
items . push (
variantItem ( {
label : ` ${ variant . name } ${ sep } ` ,
2023-05-02 09:44:37 +00:00
detail : variant
. selectors ( )
. map ( ( selector ) = > addPixelEquivalentsToMediaQuery ( selector , rootFontSize ) )
. join ( ', ' ) ,
2023-01-27 10:30:27 +00:00
textEditText : resultingVariants [ resultingVariants . length - 1 ] + sep ,
2022-10-17 17:05:04 +00:00
additionalTextEdits :
shouldSortVariants && resultingVariants . length > 1
? [
{
newText :
resultingVariants . slice ( 0 , resultingVariants . length - 1 ) . join ( sep ) +
sep ,
range : {
2023-01-27 17:11:37 +00:00
start : {
. . . classListRange . start ,
character : classListRange.end.character - partialClassName . length ,
} ,
2022-10-17 17:05:04 +00:00
end : {
. . . replacementRange . start ,
character : replacementRange.start.character ,
} ,
2021-05-03 17:00:04 +00:00
} ,
} ,
2022-10-17 17:05:04 +00:00
]
: [ ] ,
} )
)
}
if ( variant . values . length ) {
items . push (
. . . variant . values
. filter ( ( value ) = > ! existingVariants . includes ( ` ${ variant . name } - ${ value } ` ) )
. map ( ( value ) = >
variantItem ( {
2022-10-19 17:18:06 +00:00
label :
value === 'DEFAULT'
? ` ${ variant . name } ${ sep } `
: ` ${ variant . name } ${ variant . hasDash ? '-' : '' } ${ value } ${ sep } ` ,
2022-10-17 17:05:04 +00:00
detail : variant.selectors ( { value } ) . join ( ', ' ) ,
} )
)
)
}
return items
} )
2021-05-03 17:00:04 +00:00
)
}
2021-10-01 13:11:45 +00:00
if ( state . classList ) {
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : items.concat (
2023-03-27 17:56:02 +00:00
state . classList . reduce < CompletionItem [ ] > ( ( items , [ className , { color } ] , index ) = > {
if (
state . blocklist ? . includes ( [ . . . existingVariants , className ] . join ( state . separator ) )
) {
return items
}
2023-01-27 10:30:27 +00:00
let kind : CompletionItemKind = color ? 16 : 21
let documentation : string | undefined
if ( color && typeof color !== 'string' ) {
documentation = culori . formatRgb ( color )
}
2023-03-27 17:56:02 +00:00
items . push ( {
2023-01-27 10:30:27 +00:00
label : className ,
kind ,
. . . ( documentation ? { documentation } : { } ) ,
sortText : naturalExpand ( index , state . classList . length ) ,
2023-03-27 17:56:02 +00:00
} )
return items
} , [ ] as CompletionItem [ ] )
2023-01-27 10:30:27 +00:00
) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
. . . ( important ? { important } : { } ) ,
variants : existingVariants ,
} ,
range : replacementRange ,
} ,
state . editor . capabilities . itemDefaults
)
}
return withDefaults (
{
2021-10-01 13:11:45 +00:00
isIncomplete : false ,
2023-01-27 10:30:27 +00:00
items : items
. concat (
Object . keys ( state . classNames . classNames )
. filter ( ( className ) = > {
let item = state . classNames . classNames [ className ]
if ( existingVariants . length === 0 ) {
return item . __info
}
return item . __info && isUtil ( item )
} )
. map ( ( className , index , classNames ) = > {
let kind : CompletionItemKind = 21
let documentation : string | undefined
const color = getColor ( state , className )
if ( color !== null ) {
kind = 16
if ( typeof color !== 'string' && ( color . alpha ? ? 1 ) !== 0 ) {
documentation = culori . formatRgb ( color )
}
}
2021-10-01 13:11:45 +00:00
2023-01-27 10:30:27 +00:00
return {
label : className ,
kind ,
. . . ( documentation ? { documentation } : { } ) ,
sortText : naturalExpand ( index , classNames . length ) ,
} as CompletionItem
} )
)
. filter ( ( item ) = > {
if ( item === null ) {
return false
}
if ( filter && ! filter ( item ) ) {
return false
2021-10-01 13:11:45 +00:00
}
2023-01-27 10:30:27 +00:00
return true
} ) ,
} ,
{
range : replacementRange ,
data : {
. . . ( state . completionItemData ? ? { } ) ,
variants : existingVariants ,
. . . ( important ? { important } : { } ) ,
} ,
} ,
state . editor . capabilities . itemDefaults
)
}
2021-10-01 13:11:45 +00:00
2023-01-27 10:30:27 +00:00
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 dlv ( subset , [ '__info' , '__rule' ] ) === 'undefined' ) {
isSubset = true
subsetKey = keys
replacementRange = {
. . . replacementRange ,
start : {
. . . replacementRange . start ,
character : replacementRange.start.character + keys . join ( sep ) . length + sep . length ,
} ,
2021-10-01 13:11:45 +00:00
}
2023-01-27 10:30:27 +00:00
break
2021-10-01 13:11:45 +00:00
}
2023-01-27 10:30:27 +00:00
}
2021-10-01 13:11:45 +00:00
2023-01-27 10:30:27 +00:00
return withDefaults (
{
2021-05-03 17:00:04 +00:00
isIncomplete : false ,
2023-01-27 10:30:27 +00:00
items : Object.keys ( isSubset ? subset : state.classNames.classNames )
. filter ( ( k ) = > k !== '__info' )
. filter ( ( className ) = > isContextItem ( state , [ . . . subsetKey , className ] ) )
. map ( ( className , index , classNames ) : CompletionItem = > {
return {
label : className + sep ,
kind : 9 ,
command : {
title : '' ,
command : 'editor.action.triggerSuggest' ,
} ,
sortText : '-' + naturalExpand ( index , classNames . length ) ,
data : {
. . . ( state . completionItemData ? ? { } ) ,
className ,
variants : subsetKey ,
} ,
}
} )
2021-05-03 17:00:04 +00:00
. concat (
2023-01-27 10:30:27 +00:00
Object . keys ( isSubset ? subset : state.classNames.classNames )
. filter ( ( className ) = >
dlv ( state . classNames . classNames , [ . . . subsetKey , className , '__info' ] )
)
. map ( ( className , index , classNames ) = > {
2021-05-03 17:00:04 +00:00
let kind : CompletionItemKind = 21
2023-01-27 10:30:27 +00:00
let documentation : string | undefined
2021-05-03 17:00:04 +00:00
const color = getColor ( state , className )
if ( color !== null ) {
kind = 16
2021-10-01 13:11:45 +00:00
if ( typeof color !== 'string' && ( color . alpha ? ? 1 ) !== 0 ) {
documentation = culori . formatRgb ( color )
2021-05-03 17:00:04 +00:00
}
}
return {
label : className ,
kind ,
2023-01-27 10:30:27 +00:00
. . . ( documentation ? { documentation } : { } ) ,
sortText : naturalExpand ( index , classNames . length ) ,
}
2021-05-03 17:00:04 +00:00
} )
)
. filter ( ( item ) = > {
if ( item === null ) {
return false
}
if ( filter && ! filter ( item ) ) {
return false
}
return true
} ) ,
2023-01-27 10:30:27 +00:00
} ,
{
range : replacementRange ,
data : {
. . . ( state . completionItemData ? ? { } ) ,
variants : subsetKey ,
} ,
} ,
state . editor . capabilities . itemDefaults
)
2020-04-11 21:20:45 +00:00
}
2021-10-08 15:51:14 +00:00
async function provideClassAttributeCompletions (
2020-04-11 21:20:45 +00:00
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
2021-05-18 11:22:18 +00:00
position : Position ,
context? : CompletionContext
2021-10-08 15:51:14 +00:00
) : Promise < CompletionList > {
2020-10-08 15:20:54 +00:00
let str = document . getText ( {
2023-04-17 11:09:05 +00:00
start : document.positionAt ( Math . max ( 0 , document . offsetAt ( position ) - 2000 ) ) ,
2020-04-11 21:20:45 +00:00
end : position ,
} )
2023-05-02 09:44:37 +00:00
let settings = ( await state . editor . getConfiguration ( document . uri ) ) . tailwindCSS
let matches = matchClassAttributes ( str , settings . classAttributes )
2020-04-11 21:20:45 +00:00
2021-10-08 15:51:14 +00:00
if ( matches . length === 0 ) {
2020-04-11 21:20:45 +00:00
return null
}
2021-10-08 15:51:14 +00:00
let match = matches [ matches . length - 1 ]
2023-01-27 17:11:37 +00:00
const lexer =
2021-10-08 15:51:14 +00:00
match [ 0 ] [ 0 ] === ':' || ( match [ 1 ] . startsWith ( '[' ) && match [ 1 ] . endsWith ( ']' ) )
2020-05-16 18:10:17 +00:00
? getComputedClassAttributeLexer ( )
: getClassAttributeLexer ( )
2023-01-27 17:11:37 +00:00
lexer . reset ( str . substr ( match . index + match [ 0 ] . length - 1 ) )
2020-05-16 18:10:17 +00:00
try {
let tokens = Array . from ( lexer )
let last = tokens [ tokens . length - 1 ]
2022-05-26 10:31:22 +00:00
if ( last . type . startsWith ( 'start' ) || last . type === 'classlist' || last . type . startsWith ( 'arb' ) ) {
2020-05-16 18:10:17 +00:00
let classList = ''
for ( let i = tokens . length - 1 ; i >= 0 ; i -- ) {
2022-05-26 10:31:22 +00:00
if ( tokens [ i ] . type === 'classlist' || tokens [ i ] . type . startsWith ( 'arb' ) ) {
2020-05-16 18:10:17 +00:00
classList = tokens [ i ] . value + classList
} else {
break
}
}
2021-05-03 17:00:04 +00:00
return completionsFromClassList (
state ,
classList ,
{
2023-01-27 17:11:37 +00:00
start : {
line : position.line ,
character : position.character - classList . length ,
} ,
2021-05-03 17:00:04 +00:00
end : position ,
2020-04-11 21:20:45 +00:00
} ,
2023-05-02 09:44:37 +00:00
settings . rootFontSize ,
2023-01-27 16:58:51 +00:00
undefined ,
2021-05-18 11:22:18 +00:00
context
2021-05-03 17:00:04 +00:00
)
2020-04-11 21:20:45 +00:00
}
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
}
2020-11-27 16:56:19 +00:00
async function provideCustomClassNameCompletions (
state : State ,
document : TextDocument ,
2023-03-13 10:29:11 +00:00
position : Position ,
context? : CompletionContext
2020-11-27 16:56:19 +00:00
) : Promise < CompletionList > {
2021-05-03 17:00:04 +00:00
const settings = await state . editor . getConfiguration ( document . uri )
2022-09-13 16:31:09 +00:00
const regexes = settings . tailwindCSS . experimental . classRegex
2020-11-27 16:56:19 +00:00
if ( regexes . length === 0 ) return null
2020-12-01 16:35:46 +00:00
const positionOffset = document . offsetAt ( position )
const searchRange : Range = {
2023-08-16 16:03:36 +00:00
start : document.positionAt ( 0 ) ,
end : document.positionAt ( positionOffset + 2000 ) ,
2020-11-27 16:56:19 +00:00
}
let str = document . getText ( searchRange )
for ( let i = 0 ; i < regexes . length ; i ++ ) {
try {
2022-08-05 15:58:50 +00:00
let [ containerRegexString , classRegexString ] = Array . isArray ( regexes [ i ] )
? regexes [ i ]
: [ regexes [ i ] ]
2020-12-01 16:35:46 +00:00
2022-08-05 15:58:50 +00:00
let containerRegex = new Regex ( containerRegexString , 'g' )
let containerMatch : ReturnType < Regex [ ' exec ' ] >
2020-11-27 16:56:19 +00:00
2020-12-02 13:52:09 +00:00
while ( ( containerMatch = containerRegex . exec ( str ) ) !== null ) {
2020-12-01 16:35:46 +00:00
const searchStart = document . offsetAt ( searchRange . start )
2022-08-05 15:58:50 +00:00
const matchStart = searchStart + containerMatch . index [ 1 ]
const matchEnd = matchStart + containerMatch [ 1 ] . length
2020-12-01 16:35:46 +00:00
const cursor = document . offsetAt ( position )
if ( cursor >= matchStart && cursor <= matchEnd ) {
2022-08-05 15:58:50 +00:00
let classList : string
2020-12-01 16:35:46 +00:00
2022-08-05 15:58:50 +00:00
if ( classRegexString ) {
let classRegex = new Regex ( classRegexString , 'g' )
let classMatch : ReturnType < Regex [ ' exec ' ] >
2020-12-01 16:35:46 +00:00
2022-08-05 15:58:50 +00:00
while ( ( classMatch = classRegex . exec ( containerMatch [ 1 ] ) ) !== null ) {
const classMatchStart = matchStart + classMatch . index [ 1 ]
const classMatchEnd = classMatchStart + classMatch [ 1 ] . length
2020-12-01 16:35:46 +00:00
if ( cursor >= classMatchStart && cursor <= classMatchEnd ) {
2022-08-05 15:58:50 +00:00
classList = classMatch [ 1 ] . substr ( 0 , cursor - classMatchStart )
2020-12-01 16:35:46 +00:00
}
2020-11-27 16:56:19 +00:00
}
2020-12-01 16:35:46 +00:00
if ( typeof classList === 'undefined' ) {
throw Error ( )
}
} else {
2022-08-05 15:58:50 +00:00
classList = containerMatch [ 1 ] . substr ( 0 , cursor - matchStart )
2020-11-27 16:56:19 +00:00
}
2023-03-13 10:29:11 +00:00
return completionsFromClassList (
state ,
classList ,
{
start : {
line : position.line ,
character : position.character - classList . length ,
} ,
end : position ,
2020-12-01 16:35:46 +00:00
} ,
2023-05-02 09:44:37 +00:00
settings . tailwindCSS . rootFontSize ,
2023-03-13 10:29:11 +00:00
undefined ,
context
)
2020-12-01 16:35:46 +00:00
}
2020-11-27 16:56:19 +00:00
}
} catch ( _ ) { }
}
return null
}
2023-05-02 09:44:37 +00:00
async function provideAtApplyCompletions (
2020-04-11 21:20:45 +00:00
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
2023-03-13 10:29:11 +00:00
position : Position ,
context? : CompletionContext
2023-05-02 09:44:37 +00:00
) : Promise < CompletionList > {
let settings = ( await state . editor . getConfiguration ( document . uri ) ) . tailwindCSS
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
} ,
2023-05-02 09:44:37 +00:00
settings . rootFontSize ,
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 (
2021-05-03 17:00:04 +00:00
semver . gte ( state . version , '2.0.0-alpha.1' ) || flagEnabled ( state , 'applyComplexClasses' )
2020-11-19 17:34:59 +00:00
)
2020-08-21 14:30:29 +00:00
}
2023-01-27 10:30:27 +00:00
let variants = item . data ? . variants ? ? [ ]
let className = item . data ? . className ? ? item . label
let validated = validateApply ( state , [ . . . variants , className ] )
2020-08-21 14:30:29 +00:00
return validated !== null && validated . isApplyable === true
2023-03-13 10:29:11 +00:00
} ,
context
2020-05-10 12:06:22 +00:00
)
2020-04-11 21:20:45 +00:00
}
2022-10-17 16:56:00 +00:00
const NUMBER_REGEX = /^(\d+\.?|\d*\.\d+)$/
function isNumber ( str : string ) : boolean {
return NUMBER_REGEX . test ( str )
}
2021-10-08 15:51:14 +00:00
async function provideClassNameCompletions (
2020-04-11 21:20:45 +00:00
state : State ,
2020-10-08 15:20:54 +00:00
document : TextDocument ,
2021-05-18 11:22:18 +00:00
position : Position ,
context? : CompletionContext
2021-10-08 15:51:14 +00:00
) : Promise < CompletionList > {
2020-10-08 15:20:54 +00:00
if ( isCssContext ( state , document , position ) ) {
2023-03-13 10:29:11 +00:00
return provideAtApplyCompletions ( state , document , position , context )
2020-04-11 21:20:45 +00:00
}
2022-03-02 17:16:35 +00:00
if ( isHtmlContext ( state , document , position ) || isJsxContext ( state , document , position ) ) {
2021-05-20 15:50:10 +00:00
return provideClassAttributeCompletions ( state , document , position , context )
}
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
2023-01-04 10:34:41 +00:00
. match ( /[\s:;/*(){}](?<helper>config|theme)\(\s*['"]?(?<path>[^)'"]*)$/ )
2020-04-11 21:20:45 +00:00
if ( match === null ) {
return null
}
2022-10-17 16:56:00 +00:00
let alpha : string
let path = match . groups . path . replace ( /^['"]+/g , '' )
let matches = path . match ( /^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]*))$/ )
if ( matches ) {
path = matches [ 1 ]
alpha = matches [ 2 ]
}
if ( alpha !== undefined ) {
return null
}
2021-05-03 17:00:04 +00:00
let base = match . groups . helper === 'config' ? state.config : dlv ( state . config , 'theme' , { } )
2022-10-17 16:56:00 +00:00
let parts = path . split ( /([\[\].]+)/ )
2020-04-11 21:20:45 +00:00
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
2022-10-17 16:56:00 +00:00
let offset : number = keys [ keys . length - 1 ] . length
2021-05-03 17:00:04 +00:00
let separator : string = separators . length ? separators [ separators . length - 1 ] : null
2020-04-11 21:20:45 +00:00
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
2022-10-17 16:56:00 +00:00
let editRange = {
start : {
line : position.line ,
character : position.character - offset ,
} ,
end : position ,
}
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : Object.keys ( obj )
. sort ( ( a , z ) = > {
let aIsNumber = isNumber ( a )
let zIsNumber = isNumber ( z )
if ( aIsNumber && ! zIsNumber ) {
return - 1
}
if ( ! aIsNumber && zIsNumber ) {
return 1
}
if ( aIsNumber && zIsNumber ) {
return parseFloat ( a ) - parseFloat ( z )
}
return 0
} )
. map ( ( item , index , items ) = > {
let color = getColorFromValue ( obj [ item ] )
const replaceDot : boolean =
item . indexOf ( '.' ) !== - 1 && separator && separator . endsWith ( '.' )
const insertClosingBrace : boolean =
text . charAt ( text . length - 1 ) !== ']' &&
( replaceDot || ( separator && separator . endsWith ( '[' ) ) )
const detail = stringifyConfigValue ( obj [ item ] )
return {
label : item ,
sortText : naturalExpand ( index , items . length ) ,
commitCharacters : [ ! item . includes ( '.' ) && '.' , ! item . includes ( '[' ) && '[' ] . filter (
Boolean
) ,
kind : color ? 16 : isObject ( obj [ item ] ) ? 9 : 10 ,
// VS Code bug causes some values to not display in some cases
detail : detail === '0' || detail === 'transparent' ? ` ${ detail } ` : detail ,
. . . ( color && typeof color !== 'string' && ( color . alpha ? ? 1 ) !== 0
? { documentation : culori.formatRgb ( color ) }
: { } ) ,
. . . ( insertClosingBrace ? { textEditText : ` ${ item } ] ` } : { } ) ,
additionalTextEdits : replaceDot
? [
{
newText : '[' ,
range : {
start : {
. . . editRange . start ,
character : editRange.start.character - 1 ,
} ,
end : editRange.start ,
2022-10-17 16:56:00 +00:00
} ,
} ,
2023-01-27 10:30:27 +00:00
]
: [ ] ,
}
} ) ,
} ,
{
range : editRange ,
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'helper' ,
} ,
} ,
state . editor . capabilities . itemDefaults
)
2020-04-11 21:20:45 +00:00
}
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
2023-01-27 10:30:27 +00:00
let items = [
semver . gte ( state . version , '1.0.0-beta.1' )
? {
label : 'base' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
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'
) } ) ` ,
} ,
}
: {
label : 'preflight' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
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
} ,
2023-01-27 10:30:27 +00:00
{
label : 'components' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
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
} ,
2023-01-27 10:30:27 +00:00
} ,
{
label : 'utilities' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
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
} ,
2023-01-27 10:30:27 +00:00
} ,
state . jit && semver . gte ( state . version , '2.1.99' )
? {
label : 'variants' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
value : ` Use this directive to control where Tailwind injects the utility variants. \ n \ nThis directive is considered an advanced escape hatch and it is recommended to omit it whenever possible. If omitted, Tailwind will append these classes to the very end of your stylesheet by default. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'just-in-time-mode#variants-are-inserted-at-tailwind-variants'
) } ) ` ,
2021-06-16 18:37:35 +00:00
} ,
2023-01-27 10:30:27 +00:00
}
: {
label : 'screens' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
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
} ,
} ,
2023-01-27 10:30:27 +00:00
]
return withDefaults (
{
isIncomplete : false ,
items : items.map ( ( item ) = > ( {
. . . item ,
kind : 21 ,
} ) ) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : '@tailwind' ,
} ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length ,
} ,
end : position ,
2020-04-27 22:52:31 +00:00
} ,
2023-01-27 10:30:27 +00:00
} ,
state . editor . capabilities . itemDefaults
)
2020-04-27 22:52:31 +00:00
}
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
}
2021-10-01 13:11:45 +00:00
if ( semver . gte ( state . version , '2.99.0' ) ) {
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
2022-10-17 17:05:04 +00:00
let possibleVariants = state . variants . flatMap ( ( variant ) = > {
if ( variant . values . length ) {
2022-10-19 17:18:06 +00:00
return variant . values . map ( ( value ) = >
value === 'DEFAULT' ? variant . name : ` ${ variant . name } ${ variant . hasDash ? '-' : '' } ${ value } `
)
2022-10-17 17:05:04 +00:00
}
return [ variant . name ]
} )
2020-04-12 22:48:57 +00:00
const existingVariants = parts . slice ( 0 , parts . length - 1 )
2021-05-17 11:38:52 +00:00
if ( state . jit ) {
possibleVariants . unshift ( 'responsive' )
possibleVariants = possibleVariants . filter ( ( v ) = > ! state . screens . includes ( v ) )
}
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : possibleVariants
. filter ( ( v ) = > existingVariants . indexOf ( v ) === - 1 )
. map ( ( variant , index , variants ) = > ( {
// TODO: detail
label : variant ,
kind : 21 ,
sortText : naturalExpand ( index , variants . length ) ,
} ) ) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'variant' ,
} ,
range : {
start : {
line : position.line ,
character : position.character - parts [ parts . length - 1 ] . length ,
2020-04-12 22:48:57 +00:00
} ,
2023-01-27 10:30:27 +00:00
end : position ,
} ,
} ,
state . editor . capabilities . itemDefaults
)
2020-04-12 22:48:57 +00:00
}
2020-11-27 17:13:46 +00:00
function provideLayerDirectiveCompletions (
state : State ,
document : TextDocument ,
position : Position
) : CompletionList {
if ( ! isCssContext ( state , document , position ) ) {
return null
}
let text = document . getText ( {
start : { line : position.line , character : 0 } ,
end : position ,
} )
const match = text . match ( /^\s*@layer\s+(?<partial>[^\s]*)$/i )
if ( match === null ) return null
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : [ 'base' , 'components' , 'utilities' ] . map ( ( layer , index , layers ) = > ( {
label : layer ,
kind : 21 ,
sortText : naturalExpand ( index , layers . length ) ,
} ) ) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'layer' ,
} ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length ,
2020-11-27 17:13:46 +00:00
} ,
2023-01-27 10:30:27 +00:00
end : position ,
2020-11-27 17:13:46 +00:00
} ,
2023-01-27 10:30:27 +00:00
} ,
state . editor . capabilities . itemDefaults
)
}
function withDefaults (
completionList : CompletionList ,
defaults : Partial < { data : any ; range : Range } > ,
supportedDefaults : string [ ]
) : CompletionList {
let defaultData = supportedDefaults . includes ( 'data' )
let defaultRange = supportedDefaults . includes ( 'editRange' )
return {
. . . completionList ,
. . . ( defaultData || defaultRange
? {
itemDefaults : {
. . . ( defaultData && defaults . data ? { data : defaults.data } : { } ) ,
. . . ( defaultRange && defaults . range ? { editRange : defaults.range } : { } ) ,
} ,
}
: { } ) ,
items :
defaultData && defaultRange
? completionList . items
: completionList . items . map ( ( { textEditText , . . . item } ) = > ( {
. . . item ,
. . . ( defaultData || ! defaults . data || item . data ? { } : { data : defaults.data } ) ,
. . . ( defaultRange || ! defaults . range
? textEditText
? { textEditText }
: { }
: {
textEdit : {
newText : textEditText ? ? item . label ,
range : defaults.range ,
} ,
} ) ,
} ) ) ,
2020-11-27 17:13:46 +00:00
}
}
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
2021-05-03 17:00:04 +00:00
const screens = dlv ( state . config , [ 'screens' ] , dlv ( state . config , [ 'theme' , 'screens' ] , { } ) )
2020-04-12 17:11:41 +00:00
if ( ! isObject ( screens ) ) return null
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : Object.keys ( screens ) . map ( ( screen , index ) = > ( {
label : screen ,
kind : 21 ,
sortText : naturalExpand ( index ) ,
} ) ) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'screen' ,
} ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length ,
2020-04-12 17:11:41 +00:00
} ,
2023-01-27 10:30:27 +00:00
end : position ,
2020-04-12 17:11:41 +00:00
} ,
2023-01-27 10:30:27 +00:00
} ,
state . editor . capabilities . itemDefaults
)
2020-04-12 17:11:41 +00:00
}
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 ,
2021-10-01 13:11:45 +00:00
value : ` Use the \` @tailwind \` directive to insert Tailwind’ s \` base \` , \` components \` , \` utilities \` and \` ${
state . jit && semver . gte ( state . version , '2.1.99' ) ? 'variants' : 'screens'
} \ ` styles into your CSS. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
2020-04-28 20:42:43 +00:00
state . version ,
'functions-and-directives/#tailwind'
) } ) ` ,
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
} ,
} ,
2020-11-27 17:13:46 +00:00
. . . ( semver . gte ( state . version , '1.8.0' )
? [
{
label : '@layer' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
value : ` Use the \` @layer \` directive to tell Tailwind which "bucket" a set of custom styles belong to. Valid layers are \` base \` , \` components \` , and \` utilities \` . \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#layer'
) } ) ` ,
} ,
} ,
]
: [ ] ) ,
2021-10-01 13:11:45 +00:00
. . . ( semver . gte ( state . version , '2.99.0' )
? [ ]
: [
{
label : '@variants' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
value : ` You can generate \` responsive \` , \` hover \` , \` focus \` , \` active \` , and other variants of your own utilities by wrapping their definitions in the \` @variants \` directive. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
state . version ,
'functions-and-directives/#variants'
) } ) ` ,
} ,
} ,
{
label : '@responsive' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
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'
) } ) ` ,
} ,
} ,
] ) ,
2022-10-17 16:59:07 +00:00
. . . ( semver . gte ( state . version , '3.2.0' )
? [
{
label : '@config' ,
documentation : {
kind : 'markdown' as typeof MarkupKind . Markdown ,
2022-10-21 11:09:57 +00:00
value : ` Use the \` @config \` directive to specify which config file Tailwind should use when compiling that CSS file. \ n \ n[Tailwind CSS Documentation]( ${ docsUrl (
2022-10-17 16:59:07 +00:00
state . version ,
'functions-and-directives/#config'
) } ) ` ,
} ,
} ,
]
: [ ] ) ,
2020-04-12 16:55:32 +00:00
]
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : items.map ( ( item ) = > ( {
. . . item ,
kind : 14 ,
} ) ) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'directive' ,
} ,
range : {
start : {
line : position.line ,
character : position.character - match . groups . partial . length - 1 ,
2020-04-12 16:55:32 +00:00
} ,
2023-01-27 10:30:27 +00:00
end : position ,
2020-04-12 16:55:32 +00:00
} ,
2023-01-27 10:30:27 +00:00
} ,
state . editor . capabilities . itemDefaults
)
2020-04-12 16:55:32 +00:00
}
2022-10-17 16:59:07 +00:00
async function provideConfigDirectiveCompletions (
state : State ,
document : TextDocument ,
position : Position
) : Promise < CompletionList > {
if ( ! isCssContext ( state , document , position ) ) {
return null
}
if ( ! semver . gte ( state . version , '3.2.0' ) ) {
return null
}
let text = document . getText ( { start : { line : position.line , character : 0 } , end : position } )
let match = text . match ( /@config\s*(?<partial>'[^']*|"[^"]*)$/ )
if ( ! match ) {
return null
}
let partial = match . groups . partial . slice ( 1 ) // remove quote
let valueBeforeLastSlash = partial . substring ( 0 , partial . lastIndexOf ( '/' ) )
let valueAfterLastSlash = partial . substring ( partial . lastIndexOf ( '/' ) + 1 )
2023-01-27 10:30:27 +00:00
return withDefaults (
{
isIncomplete : false ,
items : ( await state . editor . readDirectory ( document , valueBeforeLastSlash || '.' ) )
. filter ( ( [ name , type ] ) = > type . isDirectory || /\.c?js$/ . test ( name ) )
. map ( ( [ name , type ] ) = > ( {
label : type.isDirectory ? name + '/' : name ,
kind : type.isDirectory ? 19 : 17 ,
command : type.isDirectory
? { command : 'editor.action.triggerSuggest' , title : '' }
: undefined ,
} ) ) ,
} ,
{
data : {
. . . ( state . completionItemData ? ? { } ) ,
_type : 'filesystem' ,
} ,
range : {
start : {
line : position.line ,
character : position.character - valueAfterLastSlash . length ,
2022-10-17 16:59:07 +00:00
} ,
2023-01-27 10:30:27 +00:00
end : position ,
} ,
} ,
state . editor . capabilities . itemDefaults
)
2022-10-17 16:59:07 +00:00
}
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 > {
2021-05-03 17:00:04 +00:00
let settings = await state . editor . getConfiguration ( document . uri )
2021-05-04 11:40:50 +00:00
if ( settings . tailwindCSS . emmetCompletions !== true ) return null
2020-06-15 13:31:04 +00:00
2022-03-02 17:16:35 +00:00
const isHtml = ! isJsDoc ( state , document ) && isHtmlContext ( state , document , position )
const isJs = isJsDoc ( state , document ) || isJsxContext ( state , document , position )
2020-12-10 17:22:06 +00:00
const syntax = isHtml ? 'html' : isJs ? 'jsx' : null
2020-04-16 21:39:16 +00:00
if ( syntax === null ) {
return null
}
2021-05-03 17:00:04 +00:00
const extractAbbreviationResults = emmetHelper . extractAbbreviation ( document , position , true )
2020-04-16 21:39:16 +00:00
if (
! extractAbbreviationResults ||
2021-05-03 17:00:04 +00:00
! emmetHelper . isAbbreviationValid ( syntax , extractAbbreviationResults . abbreviation )
2020-04-16 21:39:16 +00:00
) {
return null
}
if (
2021-05-03 17:00:04 +00:00
! isValidLocationForEmmetAbbreviation ( document , extractAbbreviationResults . abbreviationRange )
2020-04-16 21:39:16 +00:00
) {
return null
}
2020-12-10 17:22:06 +00:00
if ( isJs ) {
const abbreviation : string = extractAbbreviationResults . abbreviation
if ( abbreviation . startsWith ( 'this.' ) ) {
return null
}
2021-05-03 17:00:04 +00:00
const symbols = await state . editor . getDocumentSymbols ( document . uri )
2020-12-10 17:22:06 +00:00
if (
symbols &&
symbols . find (
( symbol ) = >
abbreviation === symbol . name ||
2021-05-03 17:00:04 +00:00
( abbreviation . startsWith ( symbol . name + '.' ) && ! />|\*|\+/ . test ( abbreviation ) )
2020-12-10 17:22:06 +00:00
)
) {
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
2023-05-02 09:44:37 +00:00
return completionsFromClassList (
state ,
parts [ parts . length - 1 ] ,
{
start : {
line : position.line ,
character : position.character - parts [ parts . length - 1 ] . length ,
} ,
end : position ,
2020-04-16 21:39:16 +00:00
} ,
2023-05-02 09:44:37 +00:00
settings . tailwindCSS . rootFontSize
)
2020-04-16 21:39:16 +00:00
}
2021-05-18 11:22:18 +00:00
export async function doComplete (
state : State ,
document : TextDocument ,
position : Position ,
context? : CompletionContext
) {
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 =
2021-10-08 15:51:14 +00:00
( await provideClassNameCompletions ( state , document , position , context ) ) ||
2020-10-08 15:20:54 +00:00
provideCssHelperCompletions ( state , document , position ) ||
provideCssDirectiveCompletions ( state , document , position ) ||
provideScreenDirectiveCompletions ( state , document , position ) ||
provideVariantsDirectiveCompletions ( state , document , position ) ||
2020-11-27 16:56:19 +00:00
provideTailwindDirectiveCompletions ( state , document , position ) ||
2020-11-27 17:13:46 +00:00
provideLayerDirectiveCompletions ( state , document , position ) ||
2022-10-17 16:59:07 +00:00
( await provideConfigDirectiveCompletions ( state , document , position ) ) ||
2023-03-13 10:29:11 +00:00
( await provideCustomClassNameCompletions ( state , document , position , context ) )
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
}
2020-11-26 20:07:39 +00:00
export async function resolveCompletionItem (
2020-04-11 21:20:45 +00:00
state : State ,
item : CompletionItem
2020-11-26 20:07:39 +00:00
) : Promise < CompletionItem > {
2023-01-27 10:30:27 +00:00
if (
[ 'helper' , 'directive' , 'variant' , 'layer' , '@tailwind' , 'filesystem' ] . includes (
item . data ? . _type
)
) {
2020-04-27 21:48:30 +00:00
return item
}
2023-01-27 10:30:27 +00:00
if ( item . data ? . _type === 'screen' ) {
2021-05-03 17:00:04 +00:00
let screens = dlv ( state . config , [ 'theme' , 'screens' ] , dlv ( state . config , [ 'screens' ] , { } ) )
2020-04-27 21:48:30 +00:00
if ( ! isObject ( screens ) ) screens = { }
item . detail = stringifyScreen ( screens [ item . label ] as Screen )
2020-04-11 21:20:45 +00:00
return item
}
2023-01-27 10:30:27 +00:00
let className = item . data ? . className ? ? item . label
if ( item . data ? . important ) {
className = ` ! ${ className } `
2021-05-18 11:22:18 +00:00
}
2023-01-27 10:30:27 +00:00
let variants = item . data ? . variants ? ? [ ]
2021-05-18 11:22:18 +00:00
2021-05-03 17:00:04 +00:00
if ( state . jit ) {
if ( item . kind === 9 ) return item
2021-05-18 11:22:18 +00:00
if ( item . detail && item . documentation ) return item
2023-01-27 10:30:27 +00:00
let { root , rules } = jit . generateRules ( state , [ [ . . . variants , className ] . join ( state . separator ) ] )
2021-05-03 17:00:04 +00:00
if ( rules . length === 0 ) return item
if ( ! item . detail ) {
if ( rules . length === 1 ) {
2021-05-20 12:24:16 +00:00
item . detail = await jit . stringifyDecls ( state , rules [ 0 ] )
2021-05-03 17:00:04 +00:00
} else {
item . detail = ` ${ rules . length } rules `
}
}
if ( ! item . documentation ) {
item . documentation = {
kind : 'markdown' as typeof MarkupKind . Markdown ,
value : [ '```css' , await jit . stringifyRoot ( state , root ) , '```' ] . join ( '\n' ) ,
}
}
return item
}
2023-01-27 10:30:27 +00:00
const rules = dlv ( state . classNames . classNames , [ . . . variants , className , '__info' ] )
2020-11-19 17:34:59 +00:00
if ( item . kind === 9 ) {
2023-01-27 10:30:27 +00:00
item . detail = state . classNames . context [ className ] . join ( ', ' )
2020-04-11 21:20:45 +00:00
} else {
2023-01-27 10:30:27 +00:00
item . detail = await getCssDetail ( state , rules )
2020-04-11 21:20:45 +00:00
if ( ! item . documentation ) {
2021-05-03 17:00:04 +00:00
const settings = await state . editor . getConfiguration ( )
2023-01-27 10:30:27 +00:00
const css = stringifyCss ( [ . . . variants , className ] . join ( ':' ) , rules , settings )
2020-04-13 00:44:43 +00:00
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
}
2022-09-13 16:31:09 +00:00
function stringifyDecls ( obj : any , settings : Settings ) : 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 ] )
2020-12-01 19:05:58 +00:00
. map ( ( value ) = > {
2023-05-02 09:44:37 +00:00
if ( settings . tailwindCSS . showPixelEquivalents ) {
value = addPixelEquivalentsToValue ( value , settings . tailwindCSS . rootFontSize )
}
return ` ${ prop } : ${ value } ; `
2020-12-01 19:05:58 +00:00
} )
2020-05-02 12:18:30 +00:00
. join ( ' ' )
)
2020-04-11 21:20:45 +00:00
. join ( ' ' )
}
2020-12-01 19:05:58 +00:00
async function getCssDetail ( state : State , className : any ) : Promise < string > {
2020-04-11 21:20:45 +00:00
if ( Array . isArray ( className ) ) {
return ` ${ className . length } rules `
}
2020-04-13 00:44:43 +00:00
if ( className . __rule === true ) {
2021-05-03 17:00:04 +00:00
const settings = await state . editor . getConfiguration ( )
2022-09-13 16:31:09 +00:00
return stringifyDecls ( removeMeta ( className ) , settings )
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
}