add hover, color decorator, linting support for classRegex setting (#129)
parent
5bd09789f4
commit
b2d47d220b
|
@ -10,7 +10,7 @@ export function registerDocumentColorProvider(state: State) {
|
|||
let doc = state.editor.documents.get(document)
|
||||
if (!doc) return { colors: [] }
|
||||
|
||||
return { colors: getDocumentColors(state, doc) }
|
||||
return { colors: await getDocumentColors(state, doc) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ import {
|
|||
} from './util/lexers'
|
||||
import { validateApply } from './util/validateApply'
|
||||
import { flagEnabled } from './util/flagEnabled'
|
||||
import MultiRegexp from 'multi-regexp2'
|
||||
import { remToPx } from './util/remToPx'
|
||||
import { createMultiRegexp } from './util/createMultiRegexp'
|
||||
|
||||
export function completionsFromClassList(
|
||||
state: State,
|
||||
|
@ -197,62 +197,6 @@ function provideClassAttributeCompletions(
|
|||
return null
|
||||
}
|
||||
|
||||
function createMultiRegexp(regexString: string) {
|
||||
let insideCharClass = false
|
||||
let captureGroupIndex = -1
|
||||
|
||||
for (let i = 0; i < regexString.length; i++) {
|
||||
if (
|
||||
!insideCharClass &&
|
||||
regexString[i] === '[' &&
|
||||
regexString[i - 1] !== '\\'
|
||||
) {
|
||||
insideCharClass = true
|
||||
} else if (
|
||||
insideCharClass &&
|
||||
regexString[i] === ']' &&
|
||||
regexString[i - 1] !== '\\'
|
||||
) {
|
||||
insideCharClass = false
|
||||
} else if (
|
||||
!insideCharClass &&
|
||||
regexString[i] === '(' &&
|
||||
regexString.substr(i + 1, 2) !== '?:'
|
||||
) {
|
||||
captureGroupIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const re = /(?:[^\\]|^)\(\?:/g
|
||||
let match: RegExpExecArray
|
||||
let nonCaptureGroupIndexes: number[] = []
|
||||
|
||||
while ((match = re.exec(regexString)) !== null) {
|
||||
if (match[0].startsWith('(')) {
|
||||
nonCaptureGroupIndexes.push(match.index)
|
||||
} else {
|
||||
nonCaptureGroupIndexes.push(match.index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const regex = new MultiRegexp(
|
||||
new RegExp(
|
||||
regexString.replace(re, (m) => m.substr(0, m.length - 2)),
|
||||
'g'
|
||||
)
|
||||
)
|
||||
|
||||
let groupIndex =
|
||||
1 + nonCaptureGroupIndexes.filter((i) => i < captureGroupIndex).length
|
||||
|
||||
return {
|
||||
exec: (str: string) => {
|
||||
return regex.execForGroup(str, groupIndex)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function provideCustomClassNameCompletions(
|
||||
state: State,
|
||||
document: TextDocument,
|
||||
|
|
|
@ -26,10 +26,10 @@ export async function doValidate(
|
|||
return settings.validate
|
||||
? [
|
||||
...(only.includes(DiagnosticKind.CssConflict)
|
||||
? getCssConflictDiagnostics(state, document, settings)
|
||||
? await getCssConflictDiagnostics(state, document, settings)
|
||||
: []),
|
||||
...(only.includes(DiagnosticKind.InvalidApply)
|
||||
? getInvalidApplyDiagnostics(state, document, settings)
|
||||
? await getInvalidApplyDiagnostics(state, document, settings)
|
||||
: []),
|
||||
...(only.includes(DiagnosticKind.InvalidScreen)
|
||||
? getInvalidScreenDiagnostics(state, document, settings)
|
||||
|
|
|
@ -10,16 +10,16 @@ import { getClassNameDecls } from '../util/getClassNameDecls'
|
|||
import { getClassNameMeta } from '../util/getClassNameMeta'
|
||||
import { equal } from '../util/array'
|
||||
|
||||
export function getCssConflictDiagnostics(
|
||||
export async function getCssConflictDiagnostics(
|
||||
state: State,
|
||||
document: TextDocument,
|
||||
settings: Settings
|
||||
): CssConflictDiagnostic[] {
|
||||
): Promise<CssConflictDiagnostic[]> {
|
||||
let severity = settings.lint.cssConflict
|
||||
if (severity === 'ignore') return []
|
||||
|
||||
let diagnostics: CssConflictDiagnostic[] = []
|
||||
const classLists = findClassListsInDocument(state, document)
|
||||
const classLists = await findClassListsInDocument(state, document)
|
||||
|
||||
classLists.forEach((classList) => {
|
||||
const classNames = getClassNamesInClassList(classList)
|
||||
|
|
|
@ -4,15 +4,21 @@ import { Settings, State } from '../util/state'
|
|||
import type { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
|
||||
import { validateApply } from '../util/validateApply'
|
||||
|
||||
export function getInvalidApplyDiagnostics(
|
||||
export async function getInvalidApplyDiagnostics(
|
||||
state: State,
|
||||
document: TextDocument,
|
||||
settings: Settings
|
||||
): InvalidApplyDiagnostic[] {
|
||||
): Promise<InvalidApplyDiagnostic[]> {
|
||||
let severity = settings.lint.invalidApply
|
||||
if (severity === 'ignore') return []
|
||||
|
||||
const classNames = findClassNamesInRange(document, undefined, 'css')
|
||||
const classNames = await findClassNamesInRange(
|
||||
state,
|
||||
document,
|
||||
undefined,
|
||||
'css',
|
||||
false
|
||||
)
|
||||
|
||||
let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => {
|
||||
let result = validateApply(state, className.className)
|
||||
|
|
|
@ -10,11 +10,11 @@ import { stringToPath } from './util/stringToPath'
|
|||
import type { TextDocument } from 'vscode-languageserver'
|
||||
const dlv = require('dlv')
|
||||
|
||||
export function getDocumentColors(state: State, document: TextDocument) {
|
||||
export async function getDocumentColors(state: State, document: TextDocument) {
|
||||
let colors = []
|
||||
if (!state.enabled) return colors
|
||||
|
||||
let classLists = findClassListsInDocument(state, document)
|
||||
let classLists = await findClassListsInDocument(state, document)
|
||||
classLists.forEach((classList) => {
|
||||
let classNames = getClassNamesInClassList(classList)
|
||||
classNames.forEach((className) => {
|
||||
|
|
|
@ -77,7 +77,7 @@ async function provideClassNameHover(
|
|||
document: TextDocument,
|
||||
position: Position
|
||||
): Promise<Hover> {
|
||||
let className = findClassNameAtPosition(state, document, position)
|
||||
let className = await findClassNameAtPosition(state, document, position)
|
||||
if (className === null) return null
|
||||
|
||||
const parts = getClassNameParts(state, className.className)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
export function createMultiRegexp(regexString: string) {
|
||||
let insideCharClass = false
|
||||
let captureGroupIndex = -1
|
||||
|
||||
for (let i = 0; i < regexString.length; i++) {
|
||||
if (
|
||||
!insideCharClass &&
|
||||
regexString[i] === '[' &&
|
||||
regexString[i - 1] !== '\\'
|
||||
) {
|
||||
insideCharClass = true
|
||||
} else if (
|
||||
insideCharClass &&
|
||||
regexString[i] === ']' &&
|
||||
regexString[i - 1] !== '\\'
|
||||
) {
|
||||
insideCharClass = false
|
||||
} else if (
|
||||
!insideCharClass &&
|
||||
regexString[i] === '(' &&
|
||||
regexString.substr(i + 1, 2) !== '?:'
|
||||
) {
|
||||
captureGroupIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const re = /(?:[^\\]|^)\(\?:/g
|
||||
let match: RegExpExecArray
|
||||
let nonCaptureGroupIndexes: number[] = []
|
||||
|
||||
while ((match = re.exec(regexString)) !== null) {
|
||||
if (match[0].startsWith('(')) {
|
||||
nonCaptureGroupIndexes.push(match.index)
|
||||
} else {
|
||||
nonCaptureGroupIndexes.push(match.index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const regex = new MultiRegexp(
|
||||
new RegExp(
|
||||
regexString.replace(re, (m) => m.substr(0, m.length - 2)),
|
||||
'g'
|
||||
)
|
||||
)
|
||||
|
||||
let groupIndex =
|
||||
1 + nonCaptureGroupIndexes.filter((i) => i < captureGroupIndex).length
|
||||
|
||||
return {
|
||||
exec: (str: string) => {
|
||||
return regex.execForGroup(str, groupIndex)
|
||||
},
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@ import {
|
|||
} from './lexers'
|
||||
import { getLanguageBoundaries } from './getLanguageBoundaries'
|
||||
import { resolveRange } from './resolveRange'
|
||||
import { getDocumentSettings } from './getDocumentSettings'
|
||||
const dlv = require('dlv')
|
||||
import { createMultiRegexp } from './createMultiRegexp'
|
||||
|
||||
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
||||
let match: RegExpMatchArray
|
||||
|
@ -77,20 +80,28 @@ export function getClassNamesInClassList({
|
|||
return names
|
||||
}
|
||||
|
||||
export function findClassNamesInRange(
|
||||
export async function findClassNamesInRange(
|
||||
state: State,
|
||||
doc: TextDocument,
|
||||
range?: Range,
|
||||
mode?: 'html' | 'css'
|
||||
): DocumentClassName[] {
|
||||
const classLists = findClassListsInRange(doc, range, mode)
|
||||
mode?: 'html' | 'css',
|
||||
includeCustom: boolean = true
|
||||
): Promise<DocumentClassName[]> {
|
||||
const classLists = await findClassListsInRange(
|
||||
state,
|
||||
doc,
|
||||
range,
|
||||
mode,
|
||||
includeCustom
|
||||
)
|
||||
return flatten(classLists.map(getClassNamesInClassList))
|
||||
}
|
||||
|
||||
export function findClassNamesInDocument(
|
||||
export async function findClassNamesInDocument(
|
||||
state: State,
|
||||
doc: TextDocument
|
||||
): DocumentClassName[] {
|
||||
const classLists = findClassListsInDocument(state, doc)
|
||||
): Promise<DocumentClassName[]> {
|
||||
const classLists = await findClassListsInDocument(state, doc)
|
||||
return flatten(classLists.map(getClassNamesInClassList))
|
||||
}
|
||||
|
||||
|
@ -130,12 +141,77 @@ export function findClassListsInCssRange(
|
|||
})
|
||||
}
|
||||
|
||||
async function findCustomClassLists(
|
||||
state: State,
|
||||
doc: TextDocument,
|
||||
range?: Range
|
||||
): Promise<DocumentClassList[]> {
|
||||
const settings = await getDocumentSettings(state, doc)
|
||||
const regexes = dlv(settings, 'experimental.classRegex', [])
|
||||
|
||||
if (!Array.isArray(regexes) || regexes.length === 0) return []
|
||||
|
||||
const text = doc.getText(range)
|
||||
const result: DocumentClassList[] = []
|
||||
|
||||
for (let i = 0; i < regexes.length; i++) {
|
||||
try {
|
||||
let [containerRegex, classRegex] = Array.isArray(regexes[i])
|
||||
? regexes[i]
|
||||
: [regexes[i]]
|
||||
|
||||
containerRegex = createMultiRegexp(containerRegex)
|
||||
let containerMatch
|
||||
|
||||
while ((containerMatch = containerRegex.exec(text)) !== null) {
|
||||
const searchStart = doc.offsetAt(
|
||||
range?.start || { line: 0, character: 0 }
|
||||
)
|
||||
const matchStart = searchStart + containerMatch.start
|
||||
const matchEnd = searchStart + containerMatch.end
|
||||
|
||||
if (classRegex) {
|
||||
classRegex = createMultiRegexp(classRegex)
|
||||
let classMatch
|
||||
|
||||
while (
|
||||
(classMatch = classRegex.exec(containerMatch.match)) !== null
|
||||
) {
|
||||
const classMatchStart = matchStart + classMatch.start
|
||||
const classMatchEnd = matchStart + classMatch.end
|
||||
result.push({
|
||||
classList: classMatch.match,
|
||||
range: {
|
||||
start: doc.positionAt(classMatchStart),
|
||||
end: doc.positionAt(classMatchEnd),
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
result.push({
|
||||
classList: containerMatch.match,
|
||||
range: {
|
||||
start: doc.positionAt(matchStart),
|
||||
end: doc.positionAt(matchEnd),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function findClassListsInHtmlRange(
|
||||
doc: TextDocument,
|
||||
range?: Range
|
||||
): DocumentClassList[] {
|
||||
const text = doc.getText(range)
|
||||
const matches = findAll(/(?:\s|:)(?:class(?:Name)?|\[ngClass\])=['"`{]/g, text)
|
||||
const matches = findAll(
|
||||
/(?:\s|:)(?:class(?:Name)?|\[ngClass\])=['"`{]/g,
|
||||
text
|
||||
)
|
||||
const result: DocumentClassList[] = []
|
||||
|
||||
matches.forEach((match) => {
|
||||
|
@ -232,21 +308,29 @@ export function findClassListsInHtmlRange(
|
|||
return result
|
||||
}
|
||||
|
||||
export function findClassListsInRange(
|
||||
export async function findClassListsInRange(
|
||||
state: State,
|
||||
doc: TextDocument,
|
||||
range?: Range,
|
||||
mode?: 'html' | 'css'
|
||||
): DocumentClassList[] {
|
||||
mode?: 'html' | 'css',
|
||||
includeCustom: boolean = true
|
||||
): Promise<DocumentClassList[]> {
|
||||
let classLists: DocumentClassList[]
|
||||
if (mode === 'css') {
|
||||
return findClassListsInCssRange(doc, range)
|
||||
classLists = findClassListsInCssRange(doc, range)
|
||||
} else {
|
||||
classLists = findClassListsInHtmlRange(doc, range)
|
||||
}
|
||||
return findClassListsInHtmlRange(doc, range)
|
||||
return [
|
||||
...classLists,
|
||||
...(includeCustom ? await findCustomClassLists(state, doc, range) : []),
|
||||
]
|
||||
}
|
||||
|
||||
export function findClassListsInDocument(
|
||||
export async function findClassListsInDocument(
|
||||
state: State,
|
||||
doc: TextDocument
|
||||
): DocumentClassList[] {
|
||||
): Promise<DocumentClassList[]> {
|
||||
if (isCssDoc(state, doc)) {
|
||||
return findClassListsInCssRange(doc)
|
||||
}
|
||||
|
@ -257,6 +341,7 @@ export function findClassListsInDocument(
|
|||
return flatten([
|
||||
...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
|
||||
...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
|
||||
await findCustomClassLists(state, doc),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -323,11 +408,11 @@ export function indexToPosition(str: string, index: number): Position {
|
|||
return { line: line - 1, character: col - 1 }
|
||||
}
|
||||
|
||||
export function findClassNameAtPosition(
|
||||
export async function findClassNameAtPosition(
|
||||
state: State,
|
||||
doc: TextDocument,
|
||||
position: Position
|
||||
): DocumentClassName {
|
||||
): Promise<DocumentClassName> {
|
||||
let classNames = []
|
||||
const searchRange = {
|
||||
start: { line: Math.max(position.line - 10, 0), character: 0 },
|
||||
|
@ -335,12 +420,12 @@ export function findClassNameAtPosition(
|
|||
}
|
||||
|
||||
if (isCssContext(state, doc, position)) {
|
||||
classNames = findClassNamesInRange(doc, searchRange, 'css')
|
||||
classNames = await findClassNamesInRange(state, doc, searchRange, 'css')
|
||||
} else if (
|
||||
isHtmlContext(state, doc, position) ||
|
||||
isJsContext(state, doc, position)
|
||||
) {
|
||||
classNames = findClassNamesInRange(doc, searchRange, 'html')
|
||||
classNames = await findClassNamesInRange(state, doc, searchRange, 'html')
|
||||
}
|
||||
|
||||
if (classNames.length === 0) {
|
||||
|
|
Loading…
Reference in New Issue