wip quick fix for invalid @apply
parent
d76119cab1
commit
e79f72bda8
|
@ -2032,6 +2032,12 @@
|
||||||
"integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=",
|
"integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"detect-indent": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"detect-newline": {
|
"detect-newline": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||||
|
|
|
@ -166,6 +166,7 @@
|
||||||
"chokidar": "^3.3.1",
|
"chokidar": "^3.3.1",
|
||||||
"concurrently": "^5.1.0",
|
"concurrently": "^5.1.0",
|
||||||
"css.escape": "^1.5.1",
|
"css.escape": "^1.5.1",
|
||||||
|
"detect-indent": "^6.0.0",
|
||||||
"dlv": "^1.1.3",
|
"dlv": "^1.1.3",
|
||||||
"dset": "^2.0.1",
|
"dset": "^2.0.1",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
|
|
|
@ -133,6 +133,10 @@ export default async function getClassNames(
|
||||||
postcss,
|
postcss,
|
||||||
browserslist,
|
browserslist,
|
||||||
}),
|
}),
|
||||||
|
modules: {
|
||||||
|
tailwindcss,
|
||||||
|
postcss,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
import {
|
|
||||||
CodeAction,
|
|
||||||
CodeActionParams,
|
|
||||||
CodeActionKind,
|
|
||||||
} from 'vscode-languageserver'
|
|
||||||
import { State } from '../util/state'
|
|
||||||
import { findLast } from '../util/find'
|
|
||||||
|
|
||||||
export function provideCodeActions(
|
|
||||||
_state: State,
|
|
||||||
params: CodeActionParams
|
|
||||||
): CodeAction[] {
|
|
||||||
if (params.context.diagnostics.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return params.context.diagnostics
|
|
||||||
.map((diagnostic) => {
|
|
||||||
let match = findLast(
|
|
||||||
/ Did you mean (?:something like )?'(?<replacement>[^']+)'\?$/g,
|
|
||||||
diagnostic.message
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: `Replace with '${match.groups.replacement}'`,
|
|
||||||
kind: CodeActionKind.QuickFix,
|
|
||||||
diagnostics: [diagnostic],
|
|
||||||
edit: {
|
|
||||||
changes: {
|
|
||||||
[params.textDocument.uri]: [
|
|
||||||
{
|
|
||||||
range: diagnostic.range,
|
|
||||||
newText: match.groups.replacement,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
}
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
import {
|
||||||
|
CodeAction,
|
||||||
|
CodeActionParams,
|
||||||
|
CodeActionKind,
|
||||||
|
Range,
|
||||||
|
TextEdit,
|
||||||
|
Diagnostic,
|
||||||
|
} from 'vscode-languageserver'
|
||||||
|
import { State } from '../../util/state'
|
||||||
|
import { findLast, findClassNamesInRange } from '../../util/find'
|
||||||
|
import { isWithinRange } from '../../util/isWithinRange'
|
||||||
|
import { getClassNameParts } from '../../util/getClassNameAtPosition'
|
||||||
|
const dlv = require('dlv')
|
||||||
|
import dset from 'dset'
|
||||||
|
import { removeRangeFromString } from '../../util/removeRangeFromString'
|
||||||
|
import detectIndent from 'detect-indent'
|
||||||
|
import { cssObjToAst } from '../../util/cssObjToAst'
|
||||||
|
import isObject from '../../../util/isObject'
|
||||||
|
|
||||||
|
export function provideCodeActions(
|
||||||
|
state: State,
|
||||||
|
params: CodeActionParams
|
||||||
|
): Promise<CodeAction[]> {
|
||||||
|
if (params.context.diagnostics.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
params.context.diagnostics
|
||||||
|
.map((diagnostic) => {
|
||||||
|
if (diagnostic.code === 'invalidApply') {
|
||||||
|
return provideInvalidApplyCodeAction(state, params, diagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
|
let match = findLast(
|
||||||
|
/ Did you mean (?:something like )?'(?<replacement>[^']+)'\?$/g,
|
||||||
|
diagnostic.message
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `Replace with '${match.groups.replacement}'`,
|
||||||
|
kind: CodeActionKind.QuickFix,
|
||||||
|
diagnostics: [diagnostic],
|
||||||
|
edit: {
|
||||||
|
changes: {
|
||||||
|
[params.textDocument.uri]: [
|
||||||
|
{
|
||||||
|
range: diagnostic.range,
|
||||||
|
newText: match.groups.replacement,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function classNameToAst(
|
||||||
|
state: State,
|
||||||
|
className: string,
|
||||||
|
selector: string = `.${className}`,
|
||||||
|
important: boolean = false
|
||||||
|
) {
|
||||||
|
const parts = getClassNameParts(state, className)
|
||||||
|
if (!parts) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const baseClassName = dlv(
|
||||||
|
state.classNames.classNames,
|
||||||
|
parts[parts.length - 1]
|
||||||
|
)
|
||||||
|
if (!baseClassName) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const info = dlv(state.classNames.classNames, parts)
|
||||||
|
let context = info.__context || []
|
||||||
|
let pseudo = info.__pseudo || []
|
||||||
|
const globalContexts = state.classNames.context
|
||||||
|
let screens = dlv(
|
||||||
|
state.config,
|
||||||
|
'theme.screens',
|
||||||
|
dlv(state.config, 'screens', {})
|
||||||
|
)
|
||||||
|
if (!isObject(screens)) screens = {}
|
||||||
|
screens = Object.keys(screens)
|
||||||
|
const path = []
|
||||||
|
|
||||||
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
|
let part = parts[i]
|
||||||
|
let common = globalContexts[part]
|
||||||
|
if (!common) return null
|
||||||
|
if (screens.includes(part)) {
|
||||||
|
path.push(`@screen ${part}`)
|
||||||
|
context = context.filter((con) => !common.includes(con))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push(...context)
|
||||||
|
|
||||||
|
let obj = {}
|
||||||
|
for (let i = 1; i <= path.length; i++) {
|
||||||
|
dset(obj, path.slice(0, i), {})
|
||||||
|
}
|
||||||
|
let rule = {
|
||||||
|
// TODO: use proper selector parser
|
||||||
|
[selector + pseudo.join('')]: {
|
||||||
|
[`@apply ${parts[parts.length - 1]}${
|
||||||
|
important ? ' !important' : ''
|
||||||
|
}`]: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (path.length) {
|
||||||
|
dset(obj, path, rule)
|
||||||
|
} else {
|
||||||
|
obj = rule
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssObjToAst(obj, state.modules.postcss)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function provideInvalidApplyCodeAction(
|
||||||
|
state: State,
|
||||||
|
params: CodeActionParams,
|
||||||
|
diagnostic: Diagnostic
|
||||||
|
): Promise<CodeAction> {
|
||||||
|
let document = state.editor.documents.get(params.textDocument.uri)
|
||||||
|
let documentText = document.getText()
|
||||||
|
const { postcss } = state.modules
|
||||||
|
let change: TextEdit
|
||||||
|
|
||||||
|
let documentClassNames = findClassNamesInRange(
|
||||||
|
document,
|
||||||
|
{
|
||||||
|
start: {
|
||||||
|
line: Math.max(0, diagnostic.range.start.line - 10),
|
||||||
|
character: 0,
|
||||||
|
},
|
||||||
|
end: { line: diagnostic.range.start.line + 10, character: 0 },
|
||||||
|
},
|
||||||
|
'css'
|
||||||
|
)
|
||||||
|
let documentClassName = documentClassNames.find((className) =>
|
||||||
|
isWithinRange(diagnostic.range.start, className.range)
|
||||||
|
)
|
||||||
|
if (!documentClassName) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let totalClassNamesInClassList = documentClassName.classList.classList.split(
|
||||||
|
/\s+/
|
||||||
|
).length
|
||||||
|
|
||||||
|
await postcss([
|
||||||
|
postcss.plugin('', (_options = {}) => {
|
||||||
|
return (root) => {
|
||||||
|
root.walkRules((rule) => {
|
||||||
|
if (change) return false
|
||||||
|
|
||||||
|
rule.walkAtRules('apply', (atRule) => {
|
||||||
|
let { start, end } = atRule.source
|
||||||
|
let range: Range = {
|
||||||
|
start: {
|
||||||
|
line: start.line - 1,
|
||||||
|
character: start.column - 1,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: end.line - 1,
|
||||||
|
character: end.column - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWithinRange(diagnostic.range.start, range)) {
|
||||||
|
// keep looking
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let className = document.getText(diagnostic.range)
|
||||||
|
let ast = classNameToAst(
|
||||||
|
state,
|
||||||
|
className,
|
||||||
|
rule.selector,
|
||||||
|
documentClassName.classList.important
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!ast) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.after(ast.nodes)
|
||||||
|
let insertedRule = rule.next()
|
||||||
|
|
||||||
|
if (totalClassNamesInClassList === 1) {
|
||||||
|
atRule.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputIndent: string
|
||||||
|
let documentIndent = detectIndent(documentText)
|
||||||
|
|
||||||
|
change = {
|
||||||
|
range: {
|
||||||
|
start: {
|
||||||
|
line: rule.source.start.line - 1,
|
||||||
|
character: rule.source.start.column - 1,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: rule.source.end.line - 1,
|
||||||
|
character: rule.source.end.column,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
newText:
|
||||||
|
rule.toString() +
|
||||||
|
(insertedRule.raws.before || '\n\n') +
|
||||||
|
insertedRule
|
||||||
|
.toString()
|
||||||
|
.replace(/\n\s*\n/g, '\n')
|
||||||
|
.replace(/(@apply [^;\n]+)$/gm, '$1;')
|
||||||
|
.replace(/([^\s^]){$/gm, '$1 {')
|
||||||
|
.replace(/^\s+/gm, (m: string) => {
|
||||||
|
if (typeof outputIndent === 'undefined') outputIndent = m
|
||||||
|
return m.replace(
|
||||||
|
new RegExp(outputIndent, 'g'),
|
||||||
|
documentIndent.indent
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]).process(documentText, { from: undefined })
|
||||||
|
|
||||||
|
if (!change) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'Extract to new rule.',
|
||||||
|
kind: CodeActionKind.QuickFix,
|
||||||
|
diagnostics: [diagnostic],
|
||||||
|
edit: {
|
||||||
|
changes: {
|
||||||
|
[params.textDocument.uri]: [
|
||||||
|
...(totalClassNamesInClassList > 1
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
range: documentClassName.classList.range,
|
||||||
|
newText: removeRangeFromString(
|
||||||
|
documentClassName.classList.classList,
|
||||||
|
documentClassName.relativeRange
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
change,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,6 +73,7 @@ function getInvalidApplyDiagnostics(
|
||||||
: DiagnosticSeverity.Warning,
|
: DiagnosticSeverity.Warning,
|
||||||
range,
|
range,
|
||||||
message,
|
message,
|
||||||
|
code: 'invalidApply',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
|
@ -230,9 +230,11 @@ connection.onHover(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
connection.onCodeAction((params: CodeActionParams): CodeAction[] => {
|
connection.onCodeAction(
|
||||||
|
(params: CodeActionParams): Promise<CodeAction[]> => {
|
||||||
if (!state.enabled) return null
|
if (!state.enabled) return null
|
||||||
return provideCodeActions(state, params)
|
return provideCodeActions(state, params)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
connection.listen()
|
connection.listen()
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
This is a modified version of the postcss-js 'parse' function which accepts the
|
||||||
|
postcss module as an argument. License below:
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright 2015 Andrey Sitnik <andrey@sitnik.ru>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var IMPORTANT = /\s*!important\s*$/i
|
||||||
|
|
||||||
|
var unitless = {
|
||||||
|
'box-flex': true,
|
||||||
|
'box-flex-group': true,
|
||||||
|
'column-count': true,
|
||||||
|
flex: true,
|
||||||
|
'flex-grow': true,
|
||||||
|
'flex-positive': true,
|
||||||
|
'flex-shrink': true,
|
||||||
|
'flex-negative': true,
|
||||||
|
'font-weight': true,
|
||||||
|
'line-clamp': true,
|
||||||
|
'line-height': true,
|
||||||
|
opacity: true,
|
||||||
|
order: true,
|
||||||
|
orphans: true,
|
||||||
|
'tab-size': true,
|
||||||
|
widows: true,
|
||||||
|
'z-index': true,
|
||||||
|
zoom: true,
|
||||||
|
'fill-opacity': true,
|
||||||
|
'stroke-dashoffset': true,
|
||||||
|
'stroke-opacity': true,
|
||||||
|
'stroke-width': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
function dashify(str) {
|
||||||
|
return str
|
||||||
|
.replace(/([A-Z])/g, '-$1')
|
||||||
|
.replace(/^ms-/, '-ms-')
|
||||||
|
.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function decl(parent, name, value, postcss) {
|
||||||
|
if (value === false || value === null) return
|
||||||
|
|
||||||
|
name = dashify(name)
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
if (value === 0 || unitless[name]) {
|
||||||
|
value = value.toString()
|
||||||
|
} else {
|
||||||
|
value = value.toString() + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'css-float') name = 'float'
|
||||||
|
|
||||||
|
if (IMPORTANT.test(value)) {
|
||||||
|
value = value.replace(IMPORTANT, '')
|
||||||
|
parent.push(postcss.decl({ prop: name, value: value, important: true }))
|
||||||
|
} else {
|
||||||
|
parent.push(postcss.decl({ prop: name, value: value }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function atRule(parent, parts, value, postcss) {
|
||||||
|
var node = postcss.atRule({ name: parts[1], params: parts[3] || '' })
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
node.nodes = []
|
||||||
|
parse(value, node, postcss)
|
||||||
|
}
|
||||||
|
parent.push(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(obj, parent, postcss) {
|
||||||
|
var name, value, node, i
|
||||||
|
for (name in obj) {
|
||||||
|
if (obj.hasOwnProperty(name)) {
|
||||||
|
value = obj[name]
|
||||||
|
if (value === null || typeof value === 'undefined') {
|
||||||
|
continue
|
||||||
|
} else if (name[0] === '@') {
|
||||||
|
var parts = name.match(/@([^\s]+)(\s+([\w\W]*)\s*)?/)
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (i = 0; i < value.length; i++) {
|
||||||
|
atRule(parent, parts, value[i], postcss)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
atRule(parent, parts, value, postcss)
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
for (i = 0; i < value.length; i++) {
|
||||||
|
decl(parent, name, value[i], postcss)
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
node = postcss.rule({ selector: name })
|
||||||
|
parse(value, node, postcss)
|
||||||
|
parent.push(node)
|
||||||
|
} else {
|
||||||
|
decl(parent, name, value, postcss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cssObjToAst(obj, postcss) {
|
||||||
|
var root = postcss.root()
|
||||||
|
parse(obj, root, postcss)
|
||||||
|
return root
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ export function findLast(re: RegExp, str: string): RegExpMatchArray {
|
||||||
export function getClassNamesInClassList({
|
export function getClassNamesInClassList({
|
||||||
classList,
|
classList,
|
||||||
range,
|
range,
|
||||||
|
important,
|
||||||
}: DocumentClassList): DocumentClassName[] {
|
}: DocumentClassList): DocumentClassName[] {
|
||||||
const parts = classList.split(/(\s+)/)
|
const parts = classList.split(/(\s+)/)
|
||||||
const names: DocumentClassName[] = []
|
const names: DocumentClassName[] = []
|
||||||
|
@ -42,6 +43,15 @@ export function getClassNamesInClassList({
|
||||||
const end = indexToPosition(classList, index + parts[i].length)
|
const end = indexToPosition(classList, index + parts[i].length)
|
||||||
names.push({
|
names.push({
|
||||||
className: parts[i],
|
className: parts[i],
|
||||||
|
classList: {
|
||||||
|
classList,
|
||||||
|
range,
|
||||||
|
important,
|
||||||
|
},
|
||||||
|
relativeRange: {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: range.start.line + start.line,
|
line: range.start.line + start.line,
|
||||||
|
@ -83,7 +93,10 @@ export function findClassListsInCssRange(
|
||||||
range?: Range
|
range?: Range
|
||||||
): DocumentClassList[] {
|
): DocumentClassList[] {
|
||||||
const text = doc.getText(range)
|
const text = doc.getText(range)
|
||||||
const matches = findAll(/(@apply\s+)(?<classList>[^;}]+)[;}]/g, text)
|
const matches = findAll(
|
||||||
|
/(@apply\s+)(?<classList>[^;}]+?)(?<important>\s*!important)?\s*[;}]/g,
|
||||||
|
text
|
||||||
|
)
|
||||||
const globalStart: Position = range ? range.start : { line: 0, character: 0 }
|
const globalStart: Position = range ? range.start : { line: 0, character: 0 }
|
||||||
|
|
||||||
return matches.map((match) => {
|
return matches.map((match) => {
|
||||||
|
@ -94,6 +107,7 @@ export function findClassListsInCssRange(
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
classList: match.groups.classList,
|
classList: match.groups.classList,
|
||||||
|
important: Boolean(match.groups.important),
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: globalStart.line + start.line,
|
line: globalStart.line + start.line,
|
||||||
|
|
|
@ -1,48 +1,6 @@
|
||||||
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
import { State } from './state'
|
||||||
import { State, DocumentClassName } from './state'
|
|
||||||
const dlv = require('dlv')
|
const dlv = require('dlv')
|
||||||
|
|
||||||
export function getClassNameAtPosition(
|
|
||||||
document: TextDocument,
|
|
||||||
position: Position
|
|
||||||
): DocumentClassName {
|
|
||||||
const range1: Range = {
|
|
||||||
start: { line: Math.max(position.line - 5, 0), character: 0 },
|
|
||||||
end: position,
|
|
||||||
}
|
|
||||||
const text1: string = document.getText(range1)
|
|
||||||
|
|
||||||
if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return null
|
|
||||||
|
|
||||||
const range2: Range = {
|
|
||||||
start: { line: Math.max(position.line - 5, 0), character: 0 },
|
|
||||||
end: { line: position.line + 1, character: position.character },
|
|
||||||
}
|
|
||||||
const text2: string = document.getText(range2)
|
|
||||||
|
|
||||||
let str: string = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0]
|
|
||||||
let matches: RegExpMatchArray = str.match(/\bclass(Name)?=["']([^"']+)$/)
|
|
||||||
|
|
||||||
if (!matches) return null
|
|
||||||
|
|
||||||
let className: string = matches[2].split(' ').pop()
|
|
||||||
if (!className) return null
|
|
||||||
|
|
||||||
let range: Range = {
|
|
||||||
start: {
|
|
||||||
line: position.line,
|
|
||||||
character:
|
|
||||||
position.character + str.length - text1.length - className.length,
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
line: position.line,
|
|
||||||
character: position.character + str.length - text1.length,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return { className, range }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClassNameParts(state: State, className: string): string[] {
|
export function getClassNameParts(state: State, className: string): string[] {
|
||||||
let separator = state.separator
|
let separator = state.separator
|
||||||
className = className.replace(/^\./, '')
|
className = className.replace(/^\./, '')
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import * as util from 'util'
|
||||||
|
|
||||||
|
export function logFull(object: any): void {
|
||||||
|
console.log(util.inspect(object, { showHidden: false, depth: null }))
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Range } from 'vscode-languageserver'
|
||||||
|
import lineColumn from 'line-column'
|
||||||
|
|
||||||
|
export function removeRangeFromString(str: string, range: Range): string {
|
||||||
|
let finder = lineColumn(str + '\n', { origin: 0 })
|
||||||
|
let start = finder.toIndex(range.start.line, range.start.character)
|
||||||
|
let end = finder.toIndex(range.end.line, range.end.character)
|
||||||
|
for (let i = start - 1; i >= 0; i--) {
|
||||||
|
if (/\s/.test(str.charAt(i))) {
|
||||||
|
start = i
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (str.substr(0, start) + str.substr(end)).trim()
|
||||||
|
}
|
|
@ -48,6 +48,10 @@ export type State = null | {
|
||||||
version?: string
|
version?: string
|
||||||
configPath?: string
|
configPath?: string
|
||||||
config?: any
|
config?: any
|
||||||
|
modules?: {
|
||||||
|
tailwindcss: any
|
||||||
|
postcss: any
|
||||||
|
}
|
||||||
separator?: string
|
separator?: string
|
||||||
plugins?: any[]
|
plugins?: any[]
|
||||||
variants?: string[]
|
variants?: string[]
|
||||||
|
@ -60,11 +64,14 @@ export type State = null | {
|
||||||
export type DocumentClassList = {
|
export type DocumentClassList = {
|
||||||
classList: string
|
classList: string
|
||||||
range: Range
|
range: Range
|
||||||
|
important?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DocumentClassName = {
|
export type DocumentClassName = {
|
||||||
className: string
|
className: string
|
||||||
range: Range
|
range: Range
|
||||||
|
relativeRange: Range
|
||||||
|
classList: DocumentClassList
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassNameMeta = {
|
export type ClassNameMeta = {
|
||||||
|
|
Loading…
Reference in New Issue