229 lines
5.2 KiB
TypeScript
229 lines
5.2 KiB
TypeScript
|
'use strict'
|
||
|
|
||
|
import * as vscode from 'vscode'
|
||
|
import { join } from 'path'
|
||
|
const tailwindClassNames = require('tailwind-class-names')
|
||
|
// const tailwindClassNames = require('/Users/brad/Code/tailwind-class-names/dist')
|
||
|
const dlv = require('dlv')
|
||
|
|
||
|
export async function activate(context: vscode.ExtensionContext) {
|
||
|
if (!vscode.workspace.name) return
|
||
|
|
||
|
const configFile = await vscode.workspace.findFiles(
|
||
|
'{tailwind,tailwind.config,.tailwindrc}.js',
|
||
|
'**/node_modules/**',
|
||
|
1
|
||
|
)
|
||
|
if (!configFile) return
|
||
|
|
||
|
const plugin = join(
|
||
|
vscode.workspace.workspaceFolders[0].uri.fsPath,
|
||
|
'node_modules',
|
||
|
'tailwindcss'
|
||
|
)
|
||
|
|
||
|
let tw
|
||
|
|
||
|
try {
|
||
|
tw = await tailwindClassNames(
|
||
|
configFile[0].fsPath,
|
||
|
{
|
||
|
tree: true,
|
||
|
strings: true
|
||
|
},
|
||
|
plugin
|
||
|
)
|
||
|
} catch (err) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
const separator = dlv(tw.config, 'options.separator', ':')
|
||
|
|
||
|
if (separator !== ':') return
|
||
|
|
||
|
const items = createItems(tw.classNames, separator, tw.config)
|
||
|
|
||
|
context.subscriptions.push(
|
||
|
createCompletionItemProvider(
|
||
|
items,
|
||
|
['typescriptreact', 'javascript', 'javascriptreact'],
|
||
|
/\btw`([^`]*)$/,
|
||
|
['`', ' ', separator],
|
||
|
tw.config
|
||
|
)
|
||
|
)
|
||
|
|
||
|
context.subscriptions.push(
|
||
|
createCompletionItemProvider(
|
||
|
items,
|
||
|
['css', 'sass', 'scss'],
|
||
|
/@apply ([^;}]*)$/,
|
||
|
['.', separator],
|
||
|
tw.config,
|
||
|
'.'
|
||
|
)
|
||
|
)
|
||
|
|
||
|
context.subscriptions.push(
|
||
|
createCompletionItemProvider(
|
||
|
items,
|
||
|
[
|
||
|
'html',
|
||
|
'jade',
|
||
|
'razor',
|
||
|
'php',
|
||
|
'blade',
|
||
|
'vue',
|
||
|
'twig',
|
||
|
'markdown',
|
||
|
'erb',
|
||
|
'handlebars',
|
||
|
'ejs',
|
||
|
// for jsx
|
||
|
'typescriptreact',
|
||
|
'javascript',
|
||
|
'javascriptreact'
|
||
|
],
|
||
|
/\bclass(Name)?=["']([^"']*)/, // /\bclass(Name)?=(["'])(?!.*?\2)/
|
||
|
["'", '"', ' ', separator],
|
||
|
tw.config
|
||
|
)
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export function deactivate() {}
|
||
|
|
||
|
function createCompletionItemProvider(
|
||
|
items,
|
||
|
languages: string[],
|
||
|
regex: RegExp,
|
||
|
triggerCharacters: string[],
|
||
|
config,
|
||
|
prefix = ''
|
||
|
): vscode.Disposable {
|
||
|
return vscode.languages.registerCompletionItemProvider(
|
||
|
languages,
|
||
|
{
|
||
|
provideCompletionItems(
|
||
|
document: vscode.TextDocument,
|
||
|
position: vscode.Position
|
||
|
): vscode.CompletionItem[] {
|
||
|
const range: vscode.Range = new vscode.Range(
|
||
|
new vscode.Position(Math.max(position.line - 5, 0), 0),
|
||
|
position
|
||
|
)
|
||
|
const text: string = document.getText(range)
|
||
|
|
||
|
let p = prefix
|
||
|
const separator = config.options.separator || ':'
|
||
|
|
||
|
const matches = text.match(regex)
|
||
|
|
||
|
if (matches) {
|
||
|
const parts = matches[matches.length - 1].split(' ')
|
||
|
const str = parts[parts.length - 1]
|
||
|
|
||
|
const pth = str
|
||
|
.replace(new RegExp(`${separator}`, 'g'), '.')
|
||
|
.replace(/\.$/, '')
|
||
|
.replace(/^\./, '')
|
||
|
.replace(/\./g, '.children.')
|
||
|
|
||
|
if (pth !== '') {
|
||
|
const itms = dlv(items, pth)
|
||
|
if (itms) {
|
||
|
return prefixItems(itms.children, str, prefix)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return prefixItems(items, str, prefix)
|
||
|
}
|
||
|
|
||
|
return []
|
||
|
}
|
||
|
},
|
||
|
...triggerCharacters
|
||
|
)
|
||
|
}
|
||
|
|
||
|
function prefixItems(items, str, prefix) {
|
||
|
const addPrefix =
|
||
|
typeof prefix !== 'undefined' && prefix !== '' && str === prefix
|
||
|
|
||
|
return Object.keys(items).map(x => {
|
||
|
const item = items[x].item
|
||
|
if (addPrefix) {
|
||
|
item.filterText = item.insertText = `${prefix}${item.label}`
|
||
|
} else {
|
||
|
item.filterText = item.insertText = item.label
|
||
|
}
|
||
|
return item
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function depthOf(obj) {
|
||
|
if (typeof obj !== 'object') return 0
|
||
|
|
||
|
let level = 1
|
||
|
|
||
|
for (let key in obj) {
|
||
|
if (!obj.hasOwnProperty(key)) continue
|
||
|
|
||
|
if (typeof obj[key] === 'object') {
|
||
|
const depth = depthOf(obj[key]) + 1
|
||
|
level = Math.max(depth, level)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return level
|
||
|
}
|
||
|
|
||
|
function createItems(classNames, separator, config, parent = '') {
|
||
|
let items = {}
|
||
|
|
||
|
Object.keys(classNames).forEach(key => {
|
||
|
if (depthOf(classNames[key]) === 0) {
|
||
|
const item = new vscode.CompletionItem(
|
||
|
key,
|
||
|
vscode.CompletionItemKind.Constant
|
||
|
)
|
||
|
if (key !== 'container' && key !== 'group') {
|
||
|
if (parent) {
|
||
|
item.detail = classNames[key].replace(
|
||
|
new RegExp(`:${parent} \{(.*?)\}`),
|
||
|
'$1'
|
||
|
)
|
||
|
} else {
|
||
|
item.detail = classNames[key]
|
||
|
}
|
||
|
}
|
||
|
items[key] = {
|
||
|
item
|
||
|
}
|
||
|
} else {
|
||
|
console.log(key)
|
||
|
const item = new vscode.CompletionItem(
|
||
|
`${key}${separator}`,
|
||
|
vscode.CompletionItemKind.Constant
|
||
|
)
|
||
|
item.command = { title: '', command: 'editor.action.triggerSuggest' }
|
||
|
if (key === 'hover' || key === 'focus' || key === 'active') {
|
||
|
item.detail = `:${key}`
|
||
|
} else if (key === 'group-hover') {
|
||
|
item.detail = '.group:hover &'
|
||
|
} else if (
|
||
|
config.screens &&
|
||
|
Object.keys(config.screens).indexOf(key) !== -1
|
||
|
) {
|
||
|
item.detail = `@media (min-width: ${config.screens[key]})`
|
||
|
}
|
||
|
items[key] = {
|
||
|
item,
|
||
|
children: createItems(classNames[key], separator, config, key)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return items
|
||
|
}
|