Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
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-----
Brad Cornes <hello@bradley.dev>
2 years ago
7 changed files, 263 additions(+), 47 deletions(-)
M package-lock.json -> package-lock.json
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",
M packages/tailwindcss-language-service/package.json -> packages/tailwindcss-language-service/package.json
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",
M packages/tailwindcss-language-service/src/completionProvider.ts -> packages/tailwindcss-language-service/src/completionProvider.ts
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(' ')
     )
M packages/tailwindcss-language-service/src/util/jit.ts -> packages/tailwindcss-language-service/src/util/jit.ts
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(' ')
 }
I packages/tailwindcss-language-service/src/util/pixelEquivalents.ts
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
D packages/tailwindcss-language-service/src/util/remToPx.ts
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
-}
M packages/tailwindcss-language-service/src/util/stringify.ts -> packages/tailwindcss-language-service/src/util/stringify.ts
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