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, 268 additions(+), 51 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": {
@@ -22934,6 +22981,24 @@ 			"integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==",
 			"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==",
+			"engines": {
 			}
 		},
 		"@evocateur/libnpmaccess": {
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
@@ -29,12 +29,14 @@ import { getClassAttributeLexer, getComputedClassAttributeLexer } from './util/lexers'
 import { validateApply } from './util/validateApply'
 import { flagEnabled } from './util/flagEnabled'
   CompletionItem,
-import { Settings, State } from './util/state'
-  CompletionItem,
 import type {
 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)
@@ -45,6 +47,7 @@ export function completionsFromClassList(
   state: State,
   classList: string,
   classListRange: Range,
+  rootFontSize: number,
   filter?: (item: CompletionItem) => boolean,
   context?: CompletionContext
 ): CompletionList {
@@ -192,8 +195,11 @@
             items.push(
               variantItem({
                 label: `${variant.name}${sep}`,
-import { isCssContext } from './util/css'
+                detail: variant
+                  .selectors()
+        let { rules } = jit.generateRules(state, [testClass])
   MarkupKind,
+                  .join(', '),
                 textEditText: resultingVariants[resultingVariants.length - 1] + sep,
                 additionalTextEdits:
                   shouldSortVariants && resultingVariants.length > 1
@@ -433,12 +439,10 @@     start: document.positionAt(Math.max(0, document.offsetAt(position) - 2000)),
     end: position,
   })
 
-  let matches = matchClassAttributes(
+  let settings = (await state.editor.getConfiguration(document.uri)).tailwindCSS
   CompletionItem,
-import type {
   MarkupKind,
-    (await state.editor.getConfiguration(document.uri)).tailwindCSS.classAttributes
-  )
+  let matches = matchClassAttributes(str, settings.classAttributes)
 
   if (matches.length === 0) {
     return null
@@ -475,6 +479,7 @@             character: position.character - classList.length,
           },
           end: position,
 import { isValidLocationForEmmetAbbreviation } from './util/isValidLocationForEmmetAbbreviation'
+        if (rules.length > 0) {
         undefined,
         context
       )
@@ -549,6 +554,7 @@                 character: position.character - classList.length,
               },
               end: position,
             },
+            settings.tailwindCSS.rootFontSize,
             undefined,
             context
           )
@@ -560,12 +566,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,
@@ -589,6 +596,7 @@         character: position.character - classList.length,
       },
       end: position,
     },
+    settings.rootFontSize,
     (item) => {
       if (item.kind === 9) {
         return (
@@ -1323,21 +1331,28 @@
   const parts = emmetItems.items[0].label.split('.')
   if (parts.length < 2) return null
 
-  CompletionList,
+  return completionsFromClassList(
+    state,
+  TextDocument,
 import { Settings, State } from './util/state'
-  CompletionList,
+  CompletionItemKind,
-  Range,
+import type {
   CompletionList,
+  Position,
   CompletionItemKind,
-  CompletionList,
+import dlv from 'dlv'
   CompletionItemKind,
+import removeMeta from './util/removeMeta'
-  CompletionList,
+  TextDocument,
 import { Settings, State } from './util/state'
+  Range,
+import * as semver from './util/semver'
   TextDocument,
+      end: position,
     },
-    end: position,
+    settings.tailwindCSS.rootFontSize
   CompletionItem,
-import { naturalExpand } from './util/naturalExpand'
+import { isHtmlContext } from './util/html'
 }
 
 export async function doComplete(
@@ -1457,17 +1472,16 @@   return props
     .map((prop) =>
       ensureArray(obj[prop])
         .map((value) => {
-  CompletionList,
   TextDocument,
+import { Settings, State } from './util/state'
   CompletionList,
-  CompletionList,
   TextDocument,
+import { Settings, State } from './util/state'
   TextDocument,
-  CompletionList,
   TextDocument,
-  Position,
+  Range,
-  CompletionList,
+        if (rules.length > 0) {
   Position,
         })
         .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,20 +41,16 @@   clone.walkAtRules('defaults', (node) => {
     node.remove()
   })
 
-  if (settings.tailwindCSS.showPixelEquivalents) {
-    clone.walkDecls((decl) => {
-import { remToPx } from './remToPx'
   return (bigIntValue > 0n) - (bigIntValue < 0n)
+}
+
 import { remToPx } from './remToPx'
-}
+export function bigSign(bigIntValue) {
-import { remToPx } from './remToPx'
+  return (bigIntValue > 0n) - (bigIntValue < 0n)
 export function generateRules(
-      }
-    })
   }
 
-  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)
@@ -73,11 +69,11 @@   let settings = await state.editor.getConfiguration(uri)
 
   let result = []
   rule.walkDecls(({ prop, value }) => {
-    let px = settings.tailwindCSS.showPixelEquivalents
-      ? remToPx(value, settings.tailwindCSS.rootFontSize)
+    if (settings.tailwindCSS.showPixelEquivalents) {
-export function bigSign(bigIntValue) {
 }
+import type { Container, Document, Root, Rule, Node, AtRule } from 'postcss'
-    result.push(`${prop}: ${value}${px ? `/* ${px} */` : ''};`)
+    }
+    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,9 +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 { remToPx } from './remToPx'
 import { Settings } from './state'
 
 export function stringifyConfigValue(x: any): string {
@@ -45,13 +46,8 @@
   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
-import { ensureArray } from './array'
 import { remToPx } from './remToPx'
-      })
+
       .join('\n')
     return `${acc}${i === 0 ? '' : '\n'}${propStr}`
   }, '')
@@ -59,6 +55,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