Skip to content

Commit c766999

Browse files
committed
Show expansion of @source globs on hover
1 parent 4c5fde4 commit c766999

File tree

3 files changed

+116
-15
lines changed

3 files changed

+116
-15
lines changed

Diff for: packages/tailwindcss-language-server/tests/hover/hover.test.js

+42-12
Original file line numberDiff line numberDiff line change
@@ -157,25 +157,28 @@ withFixture('basic', (c) => {
157157
})
158158

159159
withFixture('v4/basic', (c) => {
160-
async function testHover(name, { text, lang, position, expected, expectedRange, settings }) {
160+
async function testHover(
161+
name,
162+
{ text, exact = false, lang, position, expected, expectedRange, settings },
163+
) {
161164
test.concurrent(name, async ({ expect }) => {
162165
let textDocument = await c.openDocument({ text, lang, settings })
163166
let res = await c.sendRequest('textDocument/hover', {
164167
textDocument,
165168
position,
166169
})
167170

168-
expect(res).toEqual(
169-
expected
170-
? {
171-
contents: {
172-
language: 'css',
173-
value: expected,
174-
},
175-
range: expectedRange,
176-
}
177-
: expected,
178-
)
171+
if (!exact && expected) {
172+
expected = {
173+
contents: {
174+
language: 'css',
175+
value: expected,
176+
},
177+
range: expectedRange,
178+
}
179+
}
180+
181+
expect(res).toEqual(expected)
179182
})
180183
}
181184

@@ -242,6 +245,33 @@ withFixture('v4/basic', (c) => {
242245
end: { line: 2, character: 18 },
243246
},
244247
})
248+
249+
testHover('css @source glob expansion', {
250+
exact: true,
251+
lang: 'css',
252+
text: `@source "../{app,components}/**/*.jsx"`,
253+
position: { line: 0, character: 23 },
254+
expected: {
255+
contents: {
256+
kind: 'markdown',
257+
value: [
258+
'**Expansion**',
259+
'```plaintext',
260+
'- ../app/**/*.jsx',
261+
'- ../components/**/*.jsx',
262+
'```',
263+
].join('\n'),
264+
},
265+
range: {
266+
start: { line: 0, character: 8 },
267+
end: { line: 0, character: 38 },
268+
},
269+
},
270+
expectedRange: {
271+
start: { line: 2, character: 9 },
272+
end: { line: 2, character: 18 },
273+
},
274+
})
245275
})
246276

247277
withFixture('v4/css-loading-js', (c) => {

Diff for: packages/tailwindcss-language-service/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@types/culori": "^2.1.0",
2020
"@types/moo": "0.5.3",
2121
"@types/semver": "7.3.10",
22+
"braces": "3.0.3",
2223
"color-name": "1.1.4",
2324
"css.escape": "1.5.1",
2425
"culori": "^4.0.1",

Diff for: packages/tailwindcss-language-service/src/hoverProvider.ts

+73-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import type { State } from './util/state'
2-
import type { Hover, Position } from 'vscode-languageserver'
2+
import type { Hover, MarkupContent, Position, Range } from 'vscode-languageserver'
33
import { stringifyCss, stringifyConfigValue } from './util/stringify'
44
import dlv from 'dlv'
55
import { isCssContext } from './util/css'
6-
import { findClassNameAtPosition, findHelperFunctionsInRange } from './util/find'
6+
import {
7+
findAll,
8+
findClassNameAtPosition,
9+
findHelperFunctionsInRange,
10+
indexToPosition,
11+
} from './util/find'
712
import { validateApply } from './util/validateApply'
813
import { getClassNameParts } from './util/getClassNameAtPosition'
914
import * as jit from './util/jit'
1015
import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics'
1116
import { isWithinRange } from './util/isWithinRange'
1217
import type { TextDocument } from 'vscode-languageserver-textdocument'
1318
import { addPixelEquivalentsToValue } from './util/pixelEquivalents'
19+
import { getTextWithoutComments } from './util/doc'
20+
import braces from 'braces'
21+
import { absoluteRange } from './util/absoluteRange'
1422

1523
export async function doHover(
1624
state: State,
@@ -19,7 +27,8 @@ export async function doHover(
1927
): Promise<Hover> {
2028
return (
2129
(await provideClassNameHover(state, document, position)) ||
22-
(await provideCssHelperHover(state, document, position))
30+
(await provideCssHelperHover(state, document, position)) ||
31+
(await provideSourceGlobHover(state, document, position))
2332
)
2433
}
2534

@@ -133,3 +142,64 @@ async function provideClassNameHover(
133142
range: className.range,
134143
}
135144
}
145+
146+
function markdown(lines: string[]): MarkupContent {
147+
return {
148+
kind: 'markdown',
149+
value: lines.join('\n'),
150+
}
151+
}
152+
153+
async function provideSourceGlobHover(
154+
state: State,
155+
document: TextDocument,
156+
position: Position,
157+
): Promise<Hover> {
158+
if (!isCssContext(state, document, position)) {
159+
return null
160+
}
161+
162+
let range = {
163+
start: { line: position.line, character: 0 },
164+
end: { line: position.line + 1, character: 0 },
165+
}
166+
167+
let text = getTextWithoutComments(document, 'css', range)
168+
169+
let pattern = /@source\s*(?<path>'[^']+'|"[^"]+")/dg
170+
171+
for (let match of findAll(pattern, text)) {
172+
let path = match.groups.path.slice(1, -1)
173+
174+
// Ignore paths that don't need brace expansion
175+
if (!path.includes('{') || !path.includes('}')) continue
176+
177+
// Ignore paths that don't contain the current position
178+
let slice: Range = absoluteRange(
179+
{
180+
start: indexToPosition(text, match.indices.groups.path[0]),
181+
end: indexToPosition(text, match.indices.groups.path[1]),
182+
},
183+
range,
184+
)
185+
186+
if (!isWithinRange(position, slice)) continue
187+
188+
// Perform brace expansion
189+
let paths = new Set(braces.expand(path))
190+
if (paths.size < 2) continue
191+
192+
return {
193+
range: slice,
194+
contents: markdown([
195+
//
196+
'**Expansion**',
197+
'```plaintext',
198+
...Array.from(paths, (path) => `- ${path}`),
199+
'```',
200+
]),
201+
}
202+
}
203+
204+
return null
205+
}

0 commit comments

Comments
 (0)