add initial hover provider
parent
adadf06518
commit
a9a0983d8b
File diff suppressed because it is too large
Load Diff
|
@ -19,6 +19,7 @@
|
|||
"@babel/register": "^7.9.0",
|
||||
"@types/node": "^13.9.3",
|
||||
"@zeit/ncc": "^0.22.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"dlv": "^1.1.3",
|
||||
"glob-exec": "^0.1.1",
|
||||
"tailwindcss-class-names": "0.0.1",
|
||||
|
|
|
@ -13,7 +13,7 @@ import { getColor, getColorFromString } from '../util/color'
|
|||
import { isHtmlContext } from '../util/html'
|
||||
import { isCssContext } from '../util/css'
|
||||
import { findLast, findJsxStrings, arrFindLast } from '../util/find'
|
||||
import { stringifyConfigValue } from '../util/stringify'
|
||||
import { stringifyConfigValue, stringifyCss } from '../util/stringify'
|
||||
import isObject from '../util/isObject'
|
||||
|
||||
function completionsFromClassList(
|
||||
|
@ -343,25 +343,6 @@ function stringifyDecls(obj: any): string {
|
|||
.join(' ')
|
||||
}
|
||||
|
||||
function stringifyCss(obj: any, indent: number = 0): string {
|
||||
let indentStr = ' '.repeat(indent)
|
||||
if (obj.__decls === true) {
|
||||
return Object.keys(removeMeta(obj))
|
||||
.reduce((acc, curr, i) => {
|
||||
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr}: ${obj[curr]};`
|
||||
}, '')
|
||||
.trim()
|
||||
}
|
||||
return Object.keys(removeMeta(obj))
|
||||
.reduce((acc, curr, i) => {
|
||||
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr} {\n${stringifyCss(
|
||||
obj[curr],
|
||||
indent + 2
|
||||
)}\n${indentStr}}`
|
||||
}, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function getCssDetail(state: State, className: any): string {
|
||||
if (Array.isArray(className)) {
|
||||
return `${className.length} rules`
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import { State } from '../util/state'
|
||||
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
|
||||
import {
|
||||
getClassNameAtPosition,
|
||||
getClassNameParts,
|
||||
} from '../util/getClassNameAtPosition'
|
||||
import { stringifyCss } from '../util/stringify'
|
||||
const dlv = require('dlv')
|
||||
import escapeClassName from 'css.escape'
|
||||
import { isHtmlContext } from '../util/html'
|
||||
|
||||
export function provideHover(
|
||||
state: State,
|
||||
params: TextDocumentPositionParams
|
||||
): Hover {
|
||||
let doc = state.editor.documents.get(params.textDocument.uri)
|
||||
|
||||
if (isHtmlContext(doc, params.position)) {
|
||||
return provideClassNameHover(state, params)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function provideClassNameHover(
|
||||
state: State,
|
||||
{ textDocument, position }: TextDocumentPositionParams
|
||||
): Hover {
|
||||
let doc = state.editor.documents.get(textDocument.uri)
|
||||
let hovered = getClassNameAtPosition(doc, position)
|
||||
if (!hovered) return null
|
||||
|
||||
const parts = getClassNameParts(state, hovered.className)
|
||||
if (parts === null) return null
|
||||
|
||||
return {
|
||||
contents: {
|
||||
language: 'css',
|
||||
value: stringifyCss(dlv(state.classNames.classNames, parts), {
|
||||
selector: augmentClassName(parts, state),
|
||||
}),
|
||||
},
|
||||
range: hovered.range,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
function augmentClassName(className: string | string[], state: State): string {
|
||||
const parts = Array.isArray(className)
|
||||
? className
|
||||
: getClassNameParts(state, className)
|
||||
const obj = dlv(state.classNames.classNames, parts)
|
||||
const pseudo = obj.__pseudo ? obj.__pseudo.join('') : ''
|
||||
const scope = obj.__scope ? `${obj.__scope} ` : ''
|
||||
return `${scope}.${escapeClassName(parts.join(state.separator))}${pseudo}`
|
||||
}
|
|
@ -13,6 +13,8 @@ import {
|
|||
InitializeResult,
|
||||
CompletionParams,
|
||||
CompletionList,
|
||||
Hover,
|
||||
TextDocumentPositionParams,
|
||||
} from 'vscode-languageserver'
|
||||
import getTailwindState from 'tailwindcss-class-names'
|
||||
import { State } from './util/state'
|
||||
|
@ -20,6 +22,7 @@ import {
|
|||
provideCompletions,
|
||||
resolveCompletionItem,
|
||||
} from './providers/completionProvider'
|
||||
import { provideHover } from './providers/hoverProvider'
|
||||
import { URI } from 'vscode-uri'
|
||||
|
||||
let state: State = null
|
||||
|
@ -62,6 +65,7 @@ connection.onInitialize(
|
|||
resolveProvider: true,
|
||||
triggerCharacters: ['"', "'", '`', ' ', '.', '[', state.separator],
|
||||
},
|
||||
hoverProvider: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -88,4 +92,10 @@ connection.onCompletionResolve(
|
|||
}
|
||||
)
|
||||
|
||||
connection.onHover(
|
||||
(params: TextDocumentPositionParams): Hover => {
|
||||
return provideHover(state, params)
|
||||
}
|
||||
)
|
||||
|
||||
connection.listen()
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
||||
import { State } from './state'
|
||||
const dlv = require('dlv')
|
||||
|
||||
export function getClassNameAtPosition(
|
||||
document: TextDocument,
|
||||
position: Position
|
||||
): { className: string; range: Range } {
|
||||
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[] {
|
||||
let separator = state.separator
|
||||
className = className.replace(/^\./, '')
|
||||
let parts: string[] = className.split(separator)
|
||||
|
||||
if (parts.length === 1) {
|
||||
return dlv(state.classNames.classNames, [className, '__rule']) === true
|
||||
? [className]
|
||||
: null
|
||||
}
|
||||
|
||||
let points = combinations('123456789'.substr(0, parts.length - 1)).map((x) =>
|
||||
x.split('').map((x) => parseInt(x, 10))
|
||||
)
|
||||
|
||||
let possibilities: string[][] = [
|
||||
[className],
|
||||
...points.map((p) => {
|
||||
let result = []
|
||||
let i = 0
|
||||
p.forEach((x) => {
|
||||
result.push(parts.slice(i, x).join('-'))
|
||||
i = x
|
||||
})
|
||||
result.push(parts.slice(i).join('-'))
|
||||
return result
|
||||
}),
|
||||
]
|
||||
|
||||
return possibilities.find((key) => {
|
||||
if (dlv(state.classNames.classNames, [...key, '__rule']) === true) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function combinations(str: string): string[] {
|
||||
let fn = function (active: string, rest: string, a: string[]) {
|
||||
if (!active && !rest) return
|
||||
if (!rest) {
|
||||
a.push(active)
|
||||
} else {
|
||||
fn(active + rest[0], rest.slice(1), a)
|
||||
fn(active, rest.slice(1), a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
return fn('', str, [])
|
||||
}
|
|
@ -1,11 +1,45 @@
|
|||
import removeMeta from './removeMeta'
|
||||
|
||||
export function stringifyConfigValue(x: any): string {
|
||||
if (typeof x === 'string') return x
|
||||
if (typeof x === 'number') return x.toString()
|
||||
if (Array.isArray(x)) {
|
||||
return x
|
||||
.filter(y => typeof y === 'string')
|
||||
.filter((y) => typeof y === 'string')
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export function stringifyCss(
|
||||
obj: any,
|
||||
{ indent = 0, selector }: { indent?: number; selector?: string } = {}
|
||||
): string {
|
||||
let indentStr = '\t'.repeat(indent)
|
||||
if (obj.__decls === true) {
|
||||
let before = ''
|
||||
let after = ''
|
||||
if (selector) {
|
||||
before = `${indentStr}${selector} {\n`
|
||||
after = `\n${indentStr}}`
|
||||
indentStr += '\t'
|
||||
}
|
||||
return (
|
||||
before +
|
||||
Object.keys(removeMeta(obj)).reduce((acc, curr, i) => {
|
||||
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr}: ${obj[curr]};`
|
||||
}, '') +
|
||||
after
|
||||
)
|
||||
}
|
||||
return Object.keys(removeMeta(obj)).reduce((acc, curr, i) => {
|
||||
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr} {\n${stringifyCss(
|
||||
obj[curr],
|
||||
{
|
||||
indent: indent + 1,
|
||||
selector,
|
||||
}
|
||||
)}\n${indentStr}}`
|
||||
}, '')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue