update hover provider to use lexer for html class attribute hovers
parent
53481192eb
commit
51a1050a26
|
@ -1,16 +1,10 @@
|
||||||
import { State, DocumentClassName } from '../util/state'
|
import { State } from '../util/state'
|
||||||
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
|
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
|
||||||
import {
|
import { getClassNameParts } from '../util/getClassNameAtPosition'
|
||||||
getClassNameAtPosition,
|
|
||||||
getClassNameParts,
|
|
||||||
} from '../util/getClassNameAtPosition'
|
|
||||||
import { stringifyCss, stringifyConfigValue } from '../util/stringify'
|
import { stringifyCss, stringifyConfigValue } from '../util/stringify'
|
||||||
const dlv = require('dlv')
|
const dlv = require('dlv')
|
||||||
import { isHtmlContext } from '../util/html'
|
|
||||||
import { isCssContext } from '../util/css'
|
import { isCssContext } from '../util/css'
|
||||||
import { isJsContext } from '../util/js'
|
import { findClassNameAtPosition } from '../util/find'
|
||||||
import { isWithinRange } from '../util/isWithinRange'
|
|
||||||
import { findClassNamesInRange } from '../util/find'
|
|
||||||
|
|
||||||
export function provideHover(
|
export function provideHover(
|
||||||
state: State,
|
state: State,
|
||||||
|
@ -75,68 +69,26 @@ function provideCssHelperHover(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideClassAttributeHover(
|
function provideClassNameHover(
|
||||||
state: State,
|
state: State,
|
||||||
{ textDocument, position }: TextDocumentPositionParams
|
{ textDocument, position }: TextDocumentPositionParams
|
||||||
): Hover {
|
): Hover {
|
||||||
let doc = state.editor.documents.get(textDocument.uri)
|
let doc = state.editor.documents.get(textDocument.uri)
|
||||||
|
|
||||||
if (
|
let className = findClassNameAtPosition(state, doc, position)
|
||||||
!isHtmlContext(state, doc, position) &&
|
if (className === null) return null
|
||||||
!isJsContext(state, doc, position)
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
|
|
||||||
let hovered = getClassNameAtPosition(doc, position)
|
const parts = getClassNameParts(state, className.className)
|
||||||
if (!hovered) return null
|
|
||||||
|
|
||||||
return classNameToHover(state, hovered)
|
|
||||||
}
|
|
||||||
|
|
||||||
function classNameToHover(
|
|
||||||
state: State,
|
|
||||||
{ className, range }: DocumentClassName
|
|
||||||
): Hover {
|
|
||||||
const parts = getClassNameParts(state, className)
|
|
||||||
if (!parts) return null
|
if (!parts) return null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contents: {
|
contents: {
|
||||||
language: 'css',
|
language: 'css',
|
||||||
value: stringifyCss(className, dlv(state.classNames.classNames, parts)),
|
value: stringifyCss(
|
||||||
|
className.className,
|
||||||
|
dlv(state.classNames.classNames, parts)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
range,
|
range: className.range,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideAtApplyHover(
|
|
||||||
state: State,
|
|
||||||
{ textDocument, position }: TextDocumentPositionParams
|
|
||||||
): Hover {
|
|
||||||
let doc = state.editor.documents.get(textDocument.uri)
|
|
||||||
|
|
||||||
if (!isCssContext(state, doc, position)) return null
|
|
||||||
|
|
||||||
const classNames = findClassNamesInRange(doc, {
|
|
||||||
start: { line: Math.max(position.line - 10, 0), character: 0 },
|
|
||||||
end: { line: position.line + 10, character: 0 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const className = classNames.find(({ range }) =>
|
|
||||||
isWithinRange(position, range)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!className) return null
|
|
||||||
|
|
||||||
return classNameToHover(state, className)
|
|
||||||
}
|
|
||||||
|
|
||||||
function provideClassNameHover(
|
|
||||||
state: State,
|
|
||||||
params: TextDocumentPositionParams
|
|
||||||
): Hover {
|
|
||||||
return (
|
|
||||||
provideClassAttributeHover(state, params) ||
|
|
||||||
provideAtApplyHover(state, params)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
||||||
import { DocumentClassName, DocumentClassList } from './state'
|
import { DocumentClassName, DocumentClassList, State } from './state'
|
||||||
import lineColumn from 'line-column'
|
import lineColumn from 'line-column'
|
||||||
|
import { isCssContext } from './css'
|
||||||
|
import { isHtmlContext } from './html'
|
||||||
|
import { isWithinRange } from './isWithinRange'
|
||||||
|
import { isJsContext } from './js'
|
||||||
|
import { getClassAttributeLexer } from './lexers'
|
||||||
|
|
||||||
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
||||||
let match: RegExpMatchArray
|
let match: RegExpMatchArray
|
||||||
|
@ -21,9 +26,10 @@ export function findLast(re: RegExp, str: string): RegExpMatchArray {
|
||||||
|
|
||||||
export function findClassNamesInRange(
|
export function findClassNamesInRange(
|
||||||
doc: TextDocument,
|
doc: TextDocument,
|
||||||
range: Range
|
range: Range,
|
||||||
|
mode: 'html' | 'css'
|
||||||
): DocumentClassName[] {
|
): DocumentClassName[] {
|
||||||
const classLists = findClassListsInRange(doc, range)
|
const classLists = findClassListsInRange(doc, range, mode)
|
||||||
return [].concat.apply(
|
return [].concat.apply(
|
||||||
[],
|
[],
|
||||||
classLists.map(({ classList, range }) => {
|
classLists.map(({ classList, range }) => {
|
||||||
|
@ -58,7 +64,7 @@ export function findClassNamesInRange(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findClassListsInRange(
|
export function findClassListsInCssRange(
|
||||||
doc: TextDocument,
|
doc: TextDocument,
|
||||||
range: Range
|
range: Range
|
||||||
): DocumentClassList[] {
|
): DocumentClassList[] {
|
||||||
|
@ -87,7 +93,146 @@ export function findClassListsInRange(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findClassListsInHtmlRange(
|
||||||
|
doc: TextDocument,
|
||||||
|
range: Range
|
||||||
|
): DocumentClassList[] {
|
||||||
|
const text = doc.getText(range)
|
||||||
|
const matches = findAll(/[\s:]class(?:Name)?=['"`{]/g, text)
|
||||||
|
const result: DocumentClassList[] = []
|
||||||
|
|
||||||
|
matches.forEach((match) => {
|
||||||
|
const subtext = text.substr(match.index + match[0].length - 1, 200)
|
||||||
|
|
||||||
|
let lexer = getClassAttributeLexer()
|
||||||
|
lexer.reset(subtext)
|
||||||
|
|
||||||
|
let classLists: { value: string; offset: number }[] = []
|
||||||
|
let token: moo.Token
|
||||||
|
let currentClassList: { value: string; offset: number }
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let token of lexer) {
|
||||||
|
if (token.type === 'classlist') {
|
||||||
|
if (currentClassList) {
|
||||||
|
currentClassList.value += token.value
|
||||||
|
} else {
|
||||||
|
currentClassList = {
|
||||||
|
value: token.value,
|
||||||
|
offset: token.offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentClassList) {
|
||||||
|
classLists.push({
|
||||||
|
value: currentClassList.value,
|
||||||
|
offset: currentClassList.offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
currentClassList = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (currentClassList) {
|
||||||
|
classLists.push({
|
||||||
|
value: currentClassList.value,
|
||||||
|
offset: currentClassList.offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(
|
||||||
|
...classLists
|
||||||
|
.map(({ value, offset }) => {
|
||||||
|
if (value.trim() === '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = value.match(/^\s*/)
|
||||||
|
const beforeOffset = before === null ? 0 : before[0].length
|
||||||
|
const after = value.match(/\s*$/)
|
||||||
|
const afterOffset = after === null ? 0 : -after[0].length
|
||||||
|
|
||||||
|
const start = indexToPosition(
|
||||||
|
text,
|
||||||
|
match.index + match[0].length - 1 + offset + beforeOffset
|
||||||
|
)
|
||||||
|
const end = indexToPosition(
|
||||||
|
text,
|
||||||
|
match.index +
|
||||||
|
match[0].length -
|
||||||
|
1 +
|
||||||
|
offset +
|
||||||
|
value.length +
|
||||||
|
afterOffset
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
classList: value,
|
||||||
|
range: {
|
||||||
|
start: {
|
||||||
|
line: range.start.line + start.line,
|
||||||
|
character: range.start.character + start.character,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: range.start.line + end.line,
|
||||||
|
character: range.start.character + end.character,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((x) => x !== null)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findClassListsInRange(
|
||||||
|
doc: TextDocument,
|
||||||
|
range: Range,
|
||||||
|
mode: 'html' | 'css'
|
||||||
|
): DocumentClassList[] {
|
||||||
|
if (mode === 'css') {
|
||||||
|
return findClassListsInCssRange(doc, range)
|
||||||
|
}
|
||||||
|
return findClassListsInHtmlRange(doc, range)
|
||||||
|
}
|
||||||
|
|
||||||
function indexToPosition(str: string, index: number): Position {
|
function indexToPosition(str: string, index: number): Position {
|
||||||
const { line, col } = lineColumn(str + '\n', index)
|
const { line, col } = lineColumn(str + '\n', index)
|
||||||
return { line: line - 1, character: col - 1 }
|
return { line: line - 1, character: col - 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findClassNameAtPosition(
|
||||||
|
state: State,
|
||||||
|
doc: TextDocument,
|
||||||
|
position: Position
|
||||||
|
): DocumentClassName {
|
||||||
|
let classNames = []
|
||||||
|
const searchRange = {
|
||||||
|
start: { line: Math.max(position.line - 10, 0), character: 0 },
|
||||||
|
end: { line: position.line + 10, character: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCssContext(state, doc, position)) {
|
||||||
|
classNames = findClassNamesInRange(doc, searchRange, 'css')
|
||||||
|
} else if (
|
||||||
|
isHtmlContext(state, doc, position) ||
|
||||||
|
isJsContext(state, doc, position)
|
||||||
|
) {
|
||||||
|
classNames = findClassNamesInRange(doc, searchRange, 'html')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classNames.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = classNames.find(({ range }) =>
|
||||||
|
isWithinRange(position, range)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!className) return null
|
||||||
|
|
||||||
|
return className
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue