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",
|
"@babel/register": "^7.9.0",
|
||||||
"@types/node": "^13.9.3",
|
"@types/node": "^13.9.3",
|
||||||
"@zeit/ncc": "^0.22.0",
|
"@zeit/ncc": "^0.22.0",
|
||||||
|
"css.escape": "^1.5.1",
|
||||||
"dlv": "^1.1.3",
|
"dlv": "^1.1.3",
|
||||||
"glob-exec": "^0.1.1",
|
"glob-exec": "^0.1.1",
|
||||||
"tailwindcss-class-names": "0.0.1",
|
"tailwindcss-class-names": "0.0.1",
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { getColor, getColorFromString } from '../util/color'
|
||||||
import { isHtmlContext } from '../util/html'
|
import { isHtmlContext } from '../util/html'
|
||||||
import { isCssContext } from '../util/css'
|
import { isCssContext } from '../util/css'
|
||||||
import { findLast, findJsxStrings, arrFindLast } from '../util/find'
|
import { findLast, findJsxStrings, arrFindLast } from '../util/find'
|
||||||
import { stringifyConfigValue } from '../util/stringify'
|
import { stringifyConfigValue, stringifyCss } from '../util/stringify'
|
||||||
import isObject from '../util/isObject'
|
import isObject from '../util/isObject'
|
||||||
|
|
||||||
function completionsFromClassList(
|
function completionsFromClassList(
|
||||||
|
@ -343,25 +343,6 @@ function stringifyDecls(obj: any): string {
|
||||||
.join(' ')
|
.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 {
|
function getCssDetail(state: State, className: any): string {
|
||||||
if (Array.isArray(className)) {
|
if (Array.isArray(className)) {
|
||||||
return `${className.length} rules`
|
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,
|
InitializeResult,
|
||||||
CompletionParams,
|
CompletionParams,
|
||||||
CompletionList,
|
CompletionList,
|
||||||
|
Hover,
|
||||||
|
TextDocumentPositionParams,
|
||||||
} from 'vscode-languageserver'
|
} from 'vscode-languageserver'
|
||||||
import getTailwindState from 'tailwindcss-class-names'
|
import getTailwindState from 'tailwindcss-class-names'
|
||||||
import { State } from './util/state'
|
import { State } from './util/state'
|
||||||
|
@ -20,6 +22,7 @@ import {
|
||||||
provideCompletions,
|
provideCompletions,
|
||||||
resolveCompletionItem,
|
resolveCompletionItem,
|
||||||
} from './providers/completionProvider'
|
} from './providers/completionProvider'
|
||||||
|
import { provideHover } from './providers/hoverProvider'
|
||||||
import { URI } from 'vscode-uri'
|
import { URI } from 'vscode-uri'
|
||||||
|
|
||||||
let state: State = null
|
let state: State = null
|
||||||
|
@ -62,6 +65,7 @@ connection.onInitialize(
|
||||||
resolveProvider: true,
|
resolveProvider: true,
|
||||||
triggerCharacters: ['"', "'", '`', ' ', '.', '[', state.separator],
|
triggerCharacters: ['"', "'", '`', ' ', '.', '[', state.separator],
|
||||||
},
|
},
|
||||||
|
hoverProvider: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,4 +92,10 @@ connection.onCompletionResolve(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
connection.onHover(
|
||||||
|
(params: TextDocumentPositionParams): Hover => {
|
||||||
|
return provideHover(state, params)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
connection.listen()
|
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 {
|
export function stringifyConfigValue(x: any): string {
|
||||||
if (typeof x === 'string') return x
|
if (typeof x === 'string') return x
|
||||||
if (typeof x === 'number') return x.toString()
|
if (typeof x === 'number') return x.toString()
|
||||||
if (Array.isArray(x)) {
|
if (Array.isArray(x)) {
|
||||||
return x
|
return x
|
||||||
.filter(y => typeof y === 'string')
|
.filter((y) => typeof y === 'string')
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
return ''
|
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