add @apply hover provider
parent
cb20c3bc86
commit
16725980b7
File diff suppressed because it is too large
Load Diff
|
@ -23,6 +23,7 @@
|
||||||
"dlv": "^1.1.3",
|
"dlv": "^1.1.3",
|
||||||
"emmet-helper": "0.0.1",
|
"emmet-helper": "0.0.1",
|
||||||
"glob-exec": "^0.1.1",
|
"glob-exec": "^0.1.1",
|
||||||
|
"line-column": "^1.0.2",
|
||||||
"tailwindcss-class-names": "0.0.1",
|
"tailwindcss-class-names": "0.0.1",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
"vscode-languageserver": "^5.2.1",
|
"vscode-languageserver": "^5.2.1",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { State } from '../util/state'
|
import { State, DocumentClassName } from '../util/state'
|
||||||
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
|
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
|
||||||
import {
|
import {
|
||||||
getClassNameAtPosition,
|
getClassNameAtPosition,
|
||||||
|
@ -9,6 +9,8 @@ const dlv = require('dlv')
|
||||||
import { isHtmlContext } from '../util/html'
|
import { isHtmlContext } from '../util/html'
|
||||||
import { isCssContext } from '../util/css'
|
import { isCssContext } from '../util/css'
|
||||||
import { isJsContext } from '../util/js'
|
import { isJsContext } from '../util/js'
|
||||||
|
import { isWithinRange } from '../util/isWithinRange'
|
||||||
|
import { findClassNamesInRange } from '../util/find'
|
||||||
|
|
||||||
export function provideHover(
|
export function provideHover(
|
||||||
state: State,
|
state: State,
|
||||||
|
@ -73,7 +75,7 @@ function provideCssHelperHover(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideClassNameHover(
|
function provideClassAttributeHover(
|
||||||
state: State,
|
state: State,
|
||||||
{ textDocument, position }: TextDocumentPositionParams
|
{ textDocument, position }: TextDocumentPositionParams
|
||||||
): Hover {
|
): Hover {
|
||||||
|
@ -84,17 +86,53 @@ function provideClassNameHover(
|
||||||
let hovered = getClassNameAtPosition(doc, position)
|
let hovered = getClassNameAtPosition(doc, position)
|
||||||
if (!hovered) return null
|
if (!hovered) return null
|
||||||
|
|
||||||
const parts = getClassNameParts(state, hovered.className)
|
return classNameToHover(state, hovered)
|
||||||
|
}
|
||||||
|
|
||||||
|
function classNameToHover(
|
||||||
|
state: State,
|
||||||
|
{ className, range }: DocumentClassName
|
||||||
|
): Hover {
|
||||||
|
const parts = getClassNameParts(state, className)
|
||||||
if (parts === null) return null
|
if (parts === null) return null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contents: {
|
contents: {
|
||||||
language: 'css',
|
language: 'css',
|
||||||
value: stringifyCss(
|
value: stringifyCss(className, dlv(state.classNames.classNames, parts)),
|
||||||
hovered.className,
|
|
||||||
dlv(state.classNames.classNames, parts)
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
range: hovered.range,
|
range,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function provideAtApplyHover(
|
||||||
|
state: State,
|
||||||
|
{ textDocument, position }: TextDocumentPositionParams
|
||||||
|
): Hover {
|
||||||
|
let doc = state.editor.documents.get(textDocument.uri)
|
||||||
|
|
||||||
|
if (!isCssContext(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,3 +1,7 @@
|
||||||
|
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
||||||
|
import { DocumentClassName, DocumentClassList } from './state'
|
||||||
|
import lineColumn from 'line-column'
|
||||||
|
|
||||||
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
||||||
let match: RegExpMatchArray
|
let match: RegExpMatchArray
|
||||||
let matches: RegExpMatchArray[] = []
|
let matches: RegExpMatchArray[] = []
|
||||||
|
@ -65,3 +69,76 @@ export function findJsxStrings(str: string): StringInfo[] {
|
||||||
}
|
}
|
||||||
return strings
|
return strings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findClassNamesInRange(
|
||||||
|
doc: TextDocument,
|
||||||
|
range: Range
|
||||||
|
): DocumentClassName[] {
|
||||||
|
const classLists = findClassListsInRange(doc, range)
|
||||||
|
return [].concat.apply(
|
||||||
|
[],
|
||||||
|
classLists.map(({ classList, range }) => {
|
||||||
|
const parts = classList.split(/(\s+)/)
|
||||||
|
const names: DocumentClassName[] = []
|
||||||
|
let index = 0
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
if (i % 2 === 0) {
|
||||||
|
const start = indexToPosition(classList, index)
|
||||||
|
const end = indexToPosition(classList, index + parts[i].length)
|
||||||
|
names.push({
|
||||||
|
className: parts[i],
|
||||||
|
range: {
|
||||||
|
start: {
|
||||||
|
line: range.start.line + start.line,
|
||||||
|
character:
|
||||||
|
(end.line === 0 ? range.start.character : 0) +
|
||||||
|
start.character,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: range.start.line + end.line,
|
||||||
|
character:
|
||||||
|
(end.line === 0 ? range.start.character : 0) + end.character,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
index += parts[i].length
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findClassListsInRange(
|
||||||
|
doc: TextDocument,
|
||||||
|
range: Range
|
||||||
|
): DocumentClassList[] {
|
||||||
|
const text = doc.getText(range)
|
||||||
|
const matches = findAll(/(@apply\s+)(?<classList>[^;}]+)[;}]/g, text)
|
||||||
|
|
||||||
|
return matches.map((match) => {
|
||||||
|
const start = indexToPosition(text, match.index + match[1].length)
|
||||||
|
const end = indexToPosition(
|
||||||
|
text,
|
||||||
|
match.index + match[1].length + match.groups.classList.length
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
classList: match.groups.classList,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexToPosition(str: string, index: number): Position {
|
||||||
|
const { line, col } = lineColumn(str + '\n', index)
|
||||||
|
return { line: line - 1, character: col - 1 }
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
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(
|
export function getClassNameAtPosition(
|
||||||
document: TextDocument,
|
document: TextDocument,
|
||||||
position: Position
|
position: Position
|
||||||
): { className: string; range: Range } {
|
): DocumentClassName {
|
||||||
const range1: Range = {
|
const range1: Range = {
|
||||||
start: { line: Math.max(position.line - 5, 0), character: 0 },
|
start: { line: Math.max(position.line - 5, 0), character: 0 },
|
||||||
end: position,
|
end: position,
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Position, Range } from 'vscode-languageserver'
|
||||||
|
|
||||||
|
export function isWithinRange(position: Position, range: Range): boolean {
|
||||||
|
if (
|
||||||
|
position.line === range.start.line &&
|
||||||
|
position.character >= range.start.character
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
position.line === range.end.line &&
|
||||||
|
position.character > range.end.character
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
position.line === range.end.line &&
|
||||||
|
position.character <= range.end.character
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
position.line === range.start.line &&
|
||||||
|
position.character < range.end.character
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position.line > range.start.line && position.line < range.end.line) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { TextDocuments, Connection } from 'vscode-languageserver'
|
import { TextDocuments, Connection, Range } from 'vscode-languageserver'
|
||||||
|
|
||||||
export type ClassNamesTree = {
|
export type ClassNamesTree = {
|
||||||
[key: string]: ClassNamesTree
|
[key: string]: ClassNamesTree
|
||||||
|
@ -36,3 +36,13 @@ export type State = null | {
|
||||||
dependencies: string[]
|
dependencies: string[]
|
||||||
editor: EditorState
|
editor: EditorState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DocumentClassList = {
|
||||||
|
classList: string
|
||||||
|
range: Range
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DocumentClassName = {
|
||||||
|
className: string
|
||||||
|
range: Range
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue