add initial hover provider

master
Brad Cornes 2020-04-12 15:48:38 +01:00
parent adadf06518
commit a9a0983d8b
7 changed files with 1504 additions and 680 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -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, [])
}

View File

@ -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}}`
}, '')
}