tailwind-ctp-intellisense @master -
refs -
log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
Include pixel equivalents in more places (#775)
* Show pixel equivalents for more `rem`/`em` values
* Add pixel equivalents to media query variant completions
Signature
-----BEGIN PGP SIGNATURE-----
wsBcBAABCAAQBQJkUNuFCRBK7hj4Ov3rIwAAzJAIAKm1iOeMURp9oEQJ8irtEF/L
GItG5UsInIR1Z/U6DK0WHvZAVOtkmgqb5Op0t4fQDxSQ09qHYAb/WnkUCvpVYVeL
j7FOjhZnNH6DLFcehar+R3V/9vjOZ/LX79HXEmyF+Tr5duNdBeXism4+j14meZJw
qBDkat6ezrJwPUeTKNa85XjV2a64EtQpA+NdGNpv0G+TpO6MZTWdbe9s8PpCpDNC
TmIQF2eCGEvtu4PJ5h43E55UpqvFBF8jy+ILKGbwaKUjT8mHnco4hE6cpidrzRIq
iccu/fwttNj2n103Vqgqe5qG/mQ2yzK82/f07al8dU1D1oFbR/fj17rj5qzU61s=
=uKc9
-----END PGP SIGNATURE-----
7 changed files, 263 additions(+), 47 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 9a9fb241028ff8d1aa3775a171eaeeda3bbbe413..f02b088a505084ae135f4f2a76ddace0b32e16dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,6 +6,9 @@ "packages": {
"": {
"name": "root",
"dependencies": {
+ "@csstools/css-parser-algorithms": "2.1.1",
+ "@csstools/css-tokenizer": "2.1.1",
+ "@csstools/media-query-list-parser": "2.0.4",
"@parcel/watcher": "2.0.3",
"@tailwindcss/aspect-ratio": "0.4.2",
"@tailwindcss/container-queries": "0.1.0",
@@ -46,6 +49,7 @@ "pkg-up": "3.1.0",
"postcss": "8.3.9",
"postcss-load-config": "3.0.1",
"postcss-selector-parser": "6.0.2",
+ "postcss-value-parser": "4.2.0",
"prettier": "2.3.0",
"resolve": "1.20.0",
"rimraf": "3.0.2",
@@ -1714,6 +1718,49 @@ "watch": "cli.js"
},
"engines": {
"node": ">=0.1.95"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz",
+ "integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==",
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^2.1.1"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz",
+ "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==",
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ },
+ "node_modules/@csstools/media-query-list-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz",
+ "integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==",
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^2.1.1",
+ "@csstools/css-tokenizer": "^2.1.1"
}
},
"node_modules/@evocateur/libnpmaccess": {
@@ -22935,6 +22982,23 @@ "requires": {
"exec-sh": "^0.3.2",
"minimist": "^1.2.0"
}
+ },
+ "@csstools/css-parser-algorithms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz",
+ "integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==",
+ "requires": {}
+ },
+ "@csstools/css-tokenizer": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz",
+ "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA=="
+ },
+ "@csstools/media-query-list-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz",
+ "integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==",
+ "requires": {}
},
"@evocateur/libnpmaccess": {
"version": "3.1.2",
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index 84a48f8f957fad62218adc29f45698ea8978c010..3471245170bc4bcd09e3fe89cbfde0f8b78e6eb2 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -14,6 +14,9 @@ "lint": "tsdx lint",
"prepublishOnly": "npm run build"
},
"dependencies": {
+ "@csstools/media-query-list-parser": "2.0.4",
+ "@csstools/css-parser-algorithms": "2.1.1",
+ "@csstools/css-tokenizer": "2.1.1",
"@types/culori": "^2.0.0",
"@types/moo": "0.5.3",
"@types/semver": "7.3.10",
@@ -28,6 +31,7 @@ "line-column": "1.0.2",
"moo": "0.5.1",
"postcss": "8.3.9",
"postcss-selector-parser": "6.0.2",
+ "postcss-value-parser": "4.2.0",
"semver": "7.3.7",
"sift-string": "0.0.2",
"stringify-object": "3.3.0",
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 89cf8f4fba739c221c0c8d1ab5b3f4621368c60c..86bb582a19235ffbfac1b582443e006089dac38f 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -28,11 +28,14 @@ import { ensureArray } from './util/array'
import { getClassAttributeLexer, getComputedClassAttributeLexer } from './util/lexers'
import { validateApply } from './util/validateApply'
import { flagEnabled } from './util/flagEnabled'
-import { remToPx } from './util/remToPx'
import * as jit from './util/jit'
import { getVariantsFromClassName } from './util/getVariantsFromClassName'
import * as culori from 'culori'
import Regex from 'becke-ch--regex--s0-0-v1--base--pl--lib'
+import {
+ addPixelEquivalentsToMediaQuery,
+ addPixelEquivalentsToValue,
+} from './util/pixelEquivalents'
let isUtil = (className) =>
Array.isArray(className.__info)
@@ -43,6 +46,7 @@ export function completionsFromClassList(
state: State,
classList: string,
classListRange: Range,
+ rootFontSize: number,
filter?: (item: CompletionItem) => boolean,
context?: CompletionContext
): CompletionList {
@@ -190,7 +194,10 @@
items.push(
variantItem({
label: `${variant.name}${sep}`,
- detail: variant.selectors().join(', '),
+ detail: variant
+ .selectors()
+ .map((selector) => addPixelEquivalentsToMediaQuery(selector, rootFontSize))
+ .join(', '),
textEditText: resultingVariants[resultingVariants.length - 1] + sep,
additionalTextEdits:
shouldSortVariants && resultingVariants.length > 1
@@ -430,10 +437,9 @@ start: document.positionAt(Math.max(0, document.offsetAt(position) - 2000)),
end: position,
})
- let matches = matchClassAttributes(
- str,
- (await state.editor.getConfiguration(document.uri)).tailwindCSS.classAttributes
- )
+ let settings = (await state.editor.getConfiguration(document.uri)).tailwindCSS
+
+ let matches = matchClassAttributes(str, settings.classAttributes)
if (matches.length === 0) {
return null
@@ -470,6 +476,7 @@ character: position.character - classList.length,
},
end: position,
},
+ settings.rootFontSize,
undefined,
context
)
@@ -544,6 +551,7 @@ character: position.character - classList.length,
},
end: position,
},
+ settings.tailwindCSS.rootFontSize,
undefined,
context
)
@@ -555,12 +563,13 @@
return null
}
-function provideAtApplyCompletions(
+async function provideAtApplyCompletions(
state: State,
document: TextDocument,
position: Position,
context?: CompletionContext
-): CompletionList {
+): Promise<CompletionList> {
+ let settings = (await state.editor.getConfiguration(document.uri)).tailwindCSS
let str = document.getText({
start: { line: Math.max(position.line - 30, 0), character: 0 },
end: position,
@@ -584,6 +593,7 @@ character: position.character - classList.length,
},
end: position,
},
+ settings.rootFontSize,
(item) => {
if (item.kind === 9) {
return (
@@ -1318,13 +1328,18 @@
const parts = emmetItems.items[0].label.split('.')
if (parts.length < 2) return null
- return completionsFromClassList(state, parts[parts.length - 1], {
- start: {
- line: position.line,
- character: position.character - parts[parts.length - 1].length,
+ return completionsFromClassList(
+ state,
+ parts[parts.length - 1],
+ {
+ start: {
+ line: position.line,
+ character: position.character - parts[parts.length - 1].length,
+ },
+ end: position,
},
- end: position,
- })
+ settings.tailwindCSS.rootFontSize
+ )
}
export async function doComplete(
@@ -1444,10 +1459,10 @@ return props
.map((prop) =>
ensureArray(obj[prop])
.map((value) => {
- const px = settings.tailwindCSS.showPixelEquivalents
- ? remToPx(value, settings.tailwindCSS.rootFontSize)
- : undefined
- return `${prop}: ${value}${px ? `/* ${px} */` : ''};`
+ if (settings.tailwindCSS.showPixelEquivalents) {
+ value = addPixelEquivalentsToValue(value, settings.tailwindCSS.rootFontSize)
+ }
+ return `${prop}: ${value};`
})
.join(' ')
)
diff --git a/packages/tailwindcss-language-service/src/util/jit.ts b/packages/tailwindcss-language-service/src/util/jit.ts
index 8ad7a309d7fffba86398963b3514c183673cc308..5dcd8d3421257aee4a41d4363641265943c8dc55 100644
--- a/packages/tailwindcss-language-service/src/util/jit.ts
+++ b/packages/tailwindcss-language-service/src/util/jit.ts
@@ -1,6 +1,6 @@
import { State } from './state'
import type { Container, Document, Root, Rule, Node, AtRule } from 'postcss'
-import { remToPx } from './remToPx'
+import { addPixelEquivalentsToCss, addPixelEquivalentsToValue } from './pixelEquivalents'
export function bigSign(bigIntValue) {
// @ts-ignore
@@ -41,17 +41,13 @@ clone.walkAtRules('defaults', (node) => {
node.remove()
})
+ let css = clone.toString()
+
if (settings.tailwindCSS.showPixelEquivalents) {
- clone.walkDecls((decl) => {
- let px = remToPx(decl.value, settings.tailwindCSS.rootFontSize)
- if (px) {
- decl.value = `${decl.value}/* ${px} */`
- }
- })
+ css = addPixelEquivalentsToCss(css, settings.tailwindCSS.rootFontSize)
}
- return clone
- .toString()
+ return css
.replace(/([^;{}\s])(\n\s*})/g, (_match, before, after) => `${before};${after}`)
.replace(/^(?: )+/gm, (indent: string) =>
' '.repeat((indent.length / 4) * settings.editor.tabSize)
@@ -70,10 +66,10 @@ let settings = await state.editor.getConfiguration(uri)
let result = []
rule.walkDecls(({ prop, value }) => {
- let px = settings.tailwindCSS.showPixelEquivalents
- ? remToPx(value, settings.tailwindCSS.rootFontSize)
- : undefined
- result.push(`${prop}: ${value}${px ? `/* ${px} */` : ''};`)
+ if (settings.tailwindCSS.showPixelEquivalents) {
+ value = addPixelEquivalentsToValue(value, settings.tailwindCSS.rootFontSize)
+ }
+ result.push(`${prop}: ${value};`)
})
return result.join(' ')
}
diff --git a/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts b/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2de7f303ebf76e4dcec7850b256f37a14ac76178
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts
@@ -0,0 +1,147 @@
+import type { Plugin } from 'postcss'
+import parseValue from 'postcss-value-parser'
+import { parse as parseMediaQueryList } from '@csstools/media-query-list-parser'
+import postcss from 'postcss'
+import { isTokenNode } from '@csstools/css-parser-algorithms'
+
+type Comment = { index: number; value: string }
+
+export function addPixelEquivalentsToValue(value: string, rootFontSize: number): string {
+ if (!value.includes('rem')) {
+ return value
+ }
+
+ parseValue(value).walk((node) => {
+ if (node.type !== 'word') {
+ return true
+ }
+
+ let unit = parseValue.unit(node.value)
+ if (!unit || unit.unit !== 'rem') {
+ return false
+ }
+
+ let commentStr = `/* ${parseFloat(unit.number) * rootFontSize}px */`
+ value = value.slice(0, node.sourceEndIndex) + commentStr + value.slice(node.sourceEndIndex)
+
+ return false
+ })
+
+ return value
+}
+
+export function addPixelEquivalentsToCss(css: string, rootFontSize: number): string {
+ if (!css.includes('em')) {
+ return css
+ }
+
+ let comments: Comment[] = []
+
+ try {
+ postcss([postcssPlugin({ comments, rootFontSize })]).process(css, { from: undefined }).css
+ } catch {
+ return css
+ }
+
+ return applyComments(css, comments)
+}
+
+function applyComments(str: string, comments: Comment[]): string {
+ let offset = 0
+
+ for (let comment of comments) {
+ let index = comment.index + offset
+ let commentStr = `/* ${comment.value} */`
+ str = str.slice(0, index) + commentStr + str.slice(index)
+ offset += commentStr.length
+ }
+
+ return str
+}
+
+function getPixelEquivalentsForMediaQuery(params: string, rootFontSize: number): Comment[] {
+ let comments: Comment[] = []
+
+ try {
+ parseMediaQueryList(params).forEach((mediaQuery) => {
+ mediaQuery.walk(({ node }) => {
+ if (
+ isTokenNode(node) &&
+ node.type === 'token' &&
+ node.value[0] === 'dimension-token' &&
+ (node.value[4].type === 'integer' || node.value[4].type === 'number') &&
+ (node.value[4].unit === 'rem' || node.value[4].unit === 'em')
+ ) {
+ comments.push({
+ index: params.length - (params.length - node.value[3] - 1),
+ value: `${node.value[4].value * rootFontSize}px`,
+ })
+ }
+ })
+ })
+ } catch {}
+
+ return comments
+}
+
+export function addPixelEquivalentsToMediaQuery(query: string, rootFontSize: number): string {
+ return query.replace(/(?<=^\s*@media\s*).*?$/, (params) => {
+ let comments = getPixelEquivalentsForMediaQuery(params, rootFontSize)
+ return applyComments(params, comments)
+ })
+}
+
+function postcssPlugin({
+ comments,
+ rootFontSize,
+}: {
+ comments: Comment[]
+ rootFontSize: number
+}): Plugin {
+ return {
+ postcssPlugin: 'plugin',
+ AtRule: {
+ media(atRule) {
+ if (!atRule.params.includes('em')) {
+ return
+ }
+
+ comments.push(
+ ...getPixelEquivalentsForMediaQuery(atRule.params, rootFontSize).map(
+ ({ index, value }) => ({
+ index: index + atRule.source.start.offset + `@media${atRule.raws.afterName}`.length,
+ value,
+ })
+ )
+ )
+ },
+ },
+ Declaration(decl) {
+ if (!decl.value.includes('rem')) {
+ return
+ }
+
+ parseValue(decl.value).walk((node) => {
+ if (node.type !== 'word') {
+ return true
+ }
+
+ let unit = parseValue.unit(node.value)
+ if (!unit || unit.unit !== 'rem') {
+ return false
+ }
+
+ comments.push({
+ index:
+ decl.source.start.offset +
+ `${decl.prop}${decl.raws.between}`.length +
+ node.sourceEndIndex,
+ value: `${parseFloat(unit.number) * rootFontSize}px`,
+ })
+
+ return false
+ })
+ },
+ }
+}
+postcssPlugin.postcss = true
diff --git a/packages/tailwindcss-language-service/src/util/remToPx.ts b/packages/tailwindcss-language-service/src/util/remToPx.ts
deleted file mode 100644
index 98cd592d98abfa5e6d0762d3a0c9412b8370e5fa..0000000000000000000000000000000000000000
--- a/packages/tailwindcss-language-service/src/util/remToPx.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export function remToPx(value: string, rootSize: number = 16): string | undefined {
- if (/^-?[0-9.]+rem$/.test(value)) {
- let number = parseFloat(value.substr(0, value.length - 3))
- if (!isNaN(number)) {
- return `${number * rootSize}px`
- }
- }
- return undefined
-}
diff --git a/packages/tailwindcss-language-service/src/util/stringify.ts b/packages/tailwindcss-language-service/src/util/stringify.ts
index 59a0e7b2c5a2b3ced4f6079a95418491a6b83434..ab1321657318935526699d1445268325f0c3ca0f 100644
--- a/packages/tailwindcss-language-service/src/util/stringify.ts
+++ b/packages/tailwindcss-language-service/src/util/stringify.ts
@@ -2,10 +2,10 @@ import removeMeta from './removeMeta'
import dlv from 'dlv'
import escapeClassName from 'css.escape'
import { ensureArray } from './array'
-import { remToPx } from './remToPx'
import stringifyObject from 'stringify-object'
import isObject from './isObject'
import { Settings } from './state'
+import { addPixelEquivalentsToCss } from './pixelEquivalents'
export function stringifyConfigValue(x: any): string {
if (isObject(x)) return `${Object.keys(x).length} values`
@@ -45,12 +45,7 @@
const indentStr = indent.repeat(context.length)
const decls = props.reduce((acc, curr, i) => {
const propStr = ensureArray(obj[curr])
- .map((val) => {
- const px = settings.tailwindCSS.showPixelEquivalents
- ? remToPx(val, settings.tailwindCSS.rootFontSize)
- : undefined
- return `${indentStr + indent}${curr}: ${val}${px ? `/* ${px} */` : ''};`
- })
+ .map((val) => `${indentStr + indent}${curr}: ${val};`)
.join('\n')
return `${acc}${i === 0 ? '' : '\n'}${propStr}`
}, '')
@@ -58,6 +53,10 @@ css += `${indentStr}${augmentClassName(className, obj)} {\n${decls}\n${indentStr}}`
for (let i = context.length - 1; i >= 0; i--) {
css += `${indent.repeat(i)}\n}`
+ }
+
+ if (settings.tailwindCSS.showPixelEquivalents) {
+ return addPixelEquivalentsToCss(css, settings.tailwindCSS.rootFontSize)
}
return css