Skip to content

Commit 1672598

Browse files
author
Brad Cornes
committed
add @apply hover provider
1 parent cb20c3b commit 1672598

File tree

7 files changed

+846
-1311
lines changed

7 files changed

+846
-1311
lines changed

packages/tailwindcss-language-server/package-lock.json

Lines changed: 675 additions & 1300 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/tailwindcss-language-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"dlv": "^1.1.3",
2424
"emmet-helper": "0.0.1",
2525
"glob-exec": "^0.1.1",
26+
"line-column": "^1.0.2",
2627
"tailwindcss-class-names": "0.0.1",
2728
"typescript": "^3.8.3",
2829
"vscode-languageserver": "^5.2.1",

packages/tailwindcss-language-server/src/providers/hoverProvider.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { State } from '../util/state'
1+
import { State, DocumentClassName } from '../util/state'
22
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
33
import {
44
getClassNameAtPosition,
@@ -9,6 +9,8 @@ const dlv = require('dlv')
99
import { isHtmlContext } from '../util/html'
1010
import { isCssContext } from '../util/css'
1111
import { isJsContext } from '../util/js'
12+
import { isWithinRange } from '../util/isWithinRange'
13+
import { findClassNamesInRange } from '../util/find'
1214

1315
export function provideHover(
1416
state: State,
@@ -73,7 +75,7 @@ function provideCssHelperHover(
7375
}
7476
}
7577

76-
function provideClassNameHover(
78+
function provideClassAttributeHover(
7779
state: State,
7880
{ textDocument, position }: TextDocumentPositionParams
7981
): Hover {
@@ -84,17 +86,53 @@ function provideClassNameHover(
8486
let hovered = getClassNameAtPosition(doc, position)
8587
if (!hovered) return null
8688

87-
const parts = getClassNameParts(state, hovered.className)
89+
return classNameToHover(state, hovered)
90+
}
91+
92+
function classNameToHover(
93+
state: State,
94+
{ className, range }: DocumentClassName
95+
): Hover {
96+
const parts = getClassNameParts(state, className)
8897
if (parts === null) return null
8998

9099
return {
91100
contents: {
92101
language: 'css',
93-
value: stringifyCss(
94-
hovered.className,
95-
dlv(state.classNames.classNames, parts)
96-
),
102+
value: stringifyCss(className, dlv(state.classNames.classNames, parts)),
97103
},
98-
range: hovered.range,
104+
range,
99105
}
100106
}
107+
108+
function provideAtApplyHover(
109+
state: State,
110+
{ textDocument, position }: TextDocumentPositionParams
111+
): Hover {
112+
let doc = state.editor.documents.get(textDocument.uri)
113+
114+
if (!isCssContext(doc, position)) return null
115+
116+
const classNames = findClassNamesInRange(doc, {
117+
start: { line: Math.max(position.line - 10, 0), character: 0 },
118+
end: { line: position.line + 10, character: 0 },
119+
})
120+
121+
const className = classNames.find(({ range }) =>
122+
isWithinRange(position, range)
123+
)
124+
125+
if (!className) return null
126+
127+
return classNameToHover(state, className)
128+
}
129+
130+
function provideClassNameHover(
131+
state: State,
132+
params: TextDocumentPositionParams
133+
): Hover {
134+
return (
135+
provideClassAttributeHover(state, params) ||
136+
provideAtApplyHover(state, params)
137+
)
138+
}

packages/tailwindcss-language-server/src/util/find.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { TextDocument, Range, Position } from 'vscode-languageserver'
2+
import { DocumentClassName, DocumentClassList } from './state'
3+
import lineColumn from 'line-column'
4+
15
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
26
let match: RegExpMatchArray
37
let matches: RegExpMatchArray[] = []
@@ -65,3 +69,76 @@ export function findJsxStrings(str: string): StringInfo[] {
6569
}
6670
return strings
6771
}
72+
73+
export function findClassNamesInRange(
74+
doc: TextDocument,
75+
range: Range
76+
): DocumentClassName[] {
77+
const classLists = findClassListsInRange(doc, range)
78+
return [].concat.apply(
79+
[],
80+
classLists.map(({ classList, range }) => {
81+
const parts = classList.split(/(\s+)/)
82+
const names: DocumentClassName[] = []
83+
let index = 0
84+
for (let i = 0; i < parts.length; i++) {
85+
if (i % 2 === 0) {
86+
const start = indexToPosition(classList, index)
87+
const end = indexToPosition(classList, index + parts[i].length)
88+
names.push({
89+
className: parts[i],
90+
range: {
91+
start: {
92+
line: range.start.line + start.line,
93+
character:
94+
(end.line === 0 ? range.start.character : 0) +
95+
start.character,
96+
},
97+
end: {
98+
line: range.start.line + end.line,
99+
character:
100+
(end.line === 0 ? range.start.character : 0) + end.character,
101+
},
102+
},
103+
})
104+
}
105+
index += parts[i].length
106+
}
107+
return names
108+
})
109+
)
110+
}
111+
112+
export function findClassListsInRange(
113+
doc: TextDocument,
114+
range: Range
115+
): DocumentClassList[] {
116+
const text = doc.getText(range)
117+
const matches = findAll(/(@apply\s+)(?<classList>[^;}]+)[;}]/g, text)
118+
119+
return matches.map((match) => {
120+
const start = indexToPosition(text, match.index + match[1].length)
121+
const end = indexToPosition(
122+
text,
123+
match.index + match[1].length + match.groups.classList.length
124+
)
125+
return {
126+
classList: match.groups.classList,
127+
range: {
128+
start: {
129+
line: range.start.line + start.line,
130+
character: range.start.character + start.character,
131+
},
132+
end: {
133+
line: range.start.line + end.line,
134+
character: range.start.character + end.character,
135+
},
136+
},
137+
}
138+
})
139+
}
140+
141+
function indexToPosition(str: string, index: number): Position {
142+
const { line, col } = lineColumn(str + '\n', index)
143+
return { line: line - 1, character: col - 1 }
144+
}

packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { TextDocument, Range, Position } from 'vscode-languageserver'
2-
import { State } from './state'
2+
import { State, DocumentClassName } from './state'
33
const dlv = require('dlv')
44

55
export function getClassNameAtPosition(
66
document: TextDocument,
77
position: Position
8-
): { className: string; range: Range } {
8+
): DocumentClassName {
99
const range1: Range = {
1010
start: { line: Math.max(position.line - 5, 0), character: 0 },
1111
end: position,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Position, Range } from 'vscode-languageserver'
2+
3+
export function isWithinRange(position: Position, range: Range): boolean {
4+
if (
5+
position.line === range.start.line &&
6+
position.character >= range.start.character
7+
) {
8+
if (
9+
position.line === range.end.line &&
10+
position.character > range.end.character
11+
) {
12+
return false
13+
} else {
14+
return true
15+
}
16+
}
17+
if (
18+
position.line === range.end.line &&
19+
position.character <= range.end.character
20+
) {
21+
if (
22+
position.line === range.start.line &&
23+
position.character < range.end.character
24+
) {
25+
return false
26+
} else {
27+
return true
28+
}
29+
}
30+
if (position.line > range.start.line && position.line < range.end.line) {
31+
return true
32+
}
33+
return false
34+
}

packages/tailwindcss-language-server/src/util/state.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TextDocuments, Connection } from 'vscode-languageserver'
1+
import { TextDocuments, Connection, Range } from 'vscode-languageserver'
22

33
export type ClassNamesTree = {
44
[key: string]: ClassNamesTree
@@ -36,3 +36,13 @@ export type State = null | {
3636
dependencies: string[]
3737
editor: EditorState
3838
}
39+
40+
export type DocumentClassList = {
41+
classList: string
42+
range: Range
43+
}
44+
45+
export type DocumentClassName = {
46+
className: string
47+
range: Range
48+
}

0 commit comments

Comments
 (0)