add @apply hover provider

master
Brad Cornes 2020-04-17 18:59:19 +01:00
parent cb20c3bc86
commit 16725980b7
7 changed files with 846 additions and 1311 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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)
)
}

View File

@ -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 }
}

View File

@ -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,

View File

@ -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
}

View File

@ -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
}