diff --git a/src/lsp/providers/hoverProvider.ts b/src/lsp/providers/hoverProvider.ts index a9010a3142cb4e9700f63e3bd06426a144b9afa0..7e27d9ac9833ec166a6332607b38562ec0b03f80 100644 --- a/src/lsp/providers/hoverProvider.ts +++ b/src/lsp/providers/hoverProvider.ts @@ -1,10 +1,16 @@ -import { State } from '../util/state' +import { State, DocumentClassName } from '../util/state' import { Hover, TextDocumentPositionParams } from 'vscode-languageserver' -import { getClassNameParts } from '../util/getClassNameAtPosition' +import { + getClassNameAtPosition, + getClassNameParts, +} from '../util/getClassNameAtPosition' import { stringifyCss, stringifyConfigValue } from '../util/stringify' const dlv = require('dlv') +import { isHtmlContext } from '../util/html' import { isCssContext } from '../util/css' -import { findClassNameAtPosition } from '../util/find' +import { isJsContext } from '../util/js' +import { isWithinRange } from '../util/isWithinRange' +import { findClassNamesInRange } from '../util/find' export function provideHover( state: State, @@ -69,26 +75,68 @@ }, } } -function provideClassNameHover( +function provideClassAttributeHover( state: State, { textDocument, position }: TextDocumentPositionParams ): Hover { let doc = state.editor.documents.get(textDocument.uri) - let className = findClassNameAtPosition(state, doc, position) - if (className === null) return null + if ( + !isHtmlContext(state, doc, position) && + !isJsContext(state, doc, position) + ) + return null - const parts = getClassNameParts(state, className.className) + let hovered = getClassNameAtPosition(doc, position) + if (!hovered) return null + + return classNameToHover(state, hovered) +} + +function classNameToHover( + state: State, + { className, range }: DocumentClassName +): Hover { + const parts = getClassNameParts(state, className) if (!parts) return null return { contents: { language: 'css', - value: stringifyCss( - className.className, - dlv(state.classNames.classNames, parts) - ), + value: stringifyCss(className, dlv(state.classNames.classNames, parts)), }, - range: className.range, + range, } } + +function provideAtApplyHover( + state: State, + { textDocument, position }: TextDocumentPositionParams +): Hover { + let doc = state.editor.documents.get(textDocument.uri) + + if (!isCssContext(state, 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) + ) +} diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts index 2fa38956102b75ea56c4e7330934c4c3f47d85e0..bd770aa37cce04fb856e76bf21b272c6c8c4f07f 100644 --- a/src/lsp/util/find.ts +++ b/src/lsp/util/find.ts @@ -1,11 +1,6 @@ import { TextDocument, Range, Position } from 'vscode-languageserver' -import { DocumentClassName, DocumentClassList, State } from './state' +import { DocumentClassName, DocumentClassList } from './state' import lineColumn from 'line-column' -import { isCssContext } from './css' -import { isHtmlContext } from './html' -import { isWithinRange } from './isWithinRange' -import { isJsContext } from './js' -import { getClassAttributeLexer } from './lexers' export function findAll(re: RegExp, str: string): RegExpMatchArray[] { let match: RegExpMatchArray @@ -26,10 +21,9 @@ } export function findClassNamesInRange( doc: TextDocument, - range: Range, - mode: 'html' | 'css' + range: Range ): DocumentClassName[] { - const classLists = findClassListsInRange(doc, range, mode) + const classLists = findClassListsInRange(doc, range) return [].concat.apply( [], classLists.map(({ classList, range }) => { @@ -64,7 +58,7 @@ }) ) } -export function findClassListsInCssRange( +export function findClassListsInRange( doc: TextDocument, range: Range ): DocumentClassList[] { @@ -93,146 +87,7 @@ } }) } -export function findClassListsInHtmlRange( - doc: TextDocument, - range: Range -): DocumentClassList[] { - const text = doc.getText(range) - const matches = findAll(/[\s:]class(?:Name)?=['"`{]/g, text) - const result: DocumentClassList[] = [] - - matches.forEach((match) => { - const subtext = text.substr(match.index + match[0].length - 1, 200) - - let lexer = getClassAttributeLexer() - lexer.reset(subtext) - - let classLists: { value: string; offset: number }[] = [] - let token: moo.Token - let currentClassList: { value: string; offset: number } - - try { - for (let token of lexer) { - if (token.type === 'classlist') { - if (currentClassList) { - currentClassList.value += token.value - } else { - currentClassList = { - value: token.value, - offset: token.offset, - } - } - } else { - if (currentClassList) { - classLists.push({ - value: currentClassList.value, - offset: currentClassList.offset, - }) - } - currentClassList = undefined - } - } - } catch (_) {} - - if (currentClassList) { - classLists.push({ - value: currentClassList.value, - offset: currentClassList.offset, - }) - } - - result.push( - ...classLists - .map(({ value, offset }) => { - if (value.trim() === '') { - return null - } - - const before = value.match(/^\s*/) - const beforeOffset = before === null ? 0 : before[0].length - const after = value.match(/\s*$/) - const afterOffset = after === null ? 0 : -after[0].length - - const start = indexToPosition( - text, - match.index + match[0].length - 1 + offset + beforeOffset - ) - const end = indexToPosition( - text, - match.index + - match[0].length - - 1 + - offset + - value.length + - afterOffset - ) - - return { - classList: value, - 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, - }, - }, - } - }) - .filter((x) => x !== null) - ) - }) - - return result -} - -export function findClassListsInRange( - doc: TextDocument, - range: Range, - mode: 'html' | 'css' -): DocumentClassList[] { - if (mode === 'css') { - return findClassListsInCssRange(doc, range) - } - return findClassListsInHtmlRange(doc, range) -} - function indexToPosition(str: string, index: number): Position { const { line, col } = lineColumn(str + '\n', index) return { line: line - 1, character: col - 1 } } - -export function findClassNameAtPosition( - state: State, - doc: TextDocument, - position: Position -): DocumentClassName { - let classNames = [] - const searchRange = { - start: { line: Math.max(position.line - 10, 0), character: 0 }, - end: { line: position.line + 10, character: 0 }, - } - - if (isCssContext(state, doc, position)) { - classNames = findClassNamesInRange(doc, searchRange, 'css') - } else if ( - isHtmlContext(state, doc, position) || - isJsContext(state, doc, position) - ) { - classNames = findClassNamesInRange(doc, searchRange, 'html') - } - - if (classNames.length === 0) { - return null - } - - const className = classNames.find(({ range }) => - isWithinRange(position, range) - ) - - if (!className) return null - - return className -}