Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
Merge branch 'color-decorators'
Brad Cornes <bradlc41@gmail.com>
4 years ago
10 changed files, 346 additions(+), 27 deletions(-)
M package-lock.json -> package-lock.json
diff --git a/package-lock.json b/package-lock.json
index 3617ee8e60c972655beb158107c47bb0ba1723bd..f79d333c85f536da05d9bdf000525a25dbbe4168 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -1019,6 +1019,12 @@ 			"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
 			"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
 			"dev": true
 		},
+		"@types/debounce": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.0.tgz",
+			"integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==",
+			"dev": true
+		},
 		"@types/graceful-fs": {
 			"version": "4.1.3",
 			"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
@@ -1958,6 +1964,12 @@ 		"date-fns": {
 			"version": "2.11.0",
 			"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.11.0.tgz",
 			"integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==",
+			"dev": true
+		},
+		"debounce": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
+			"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==",
 			"dev": true
 		},
 		"debug": {
M package.json -> package.json
diff --git a/package.json b/package.json
index 0cdb709c6539c495c4114edaaf2222479b237252..7815ce18bfdbad0cb2f858a749d55aecc269c885 100755
--- a/package.json
+++ b/package.json
@@ -71,6 +71,22 @@           },
           "default": {},
           "markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`"
         },
+        "tailwindCSS.colorDecorators": {
+          "type": "string",
+          "enum": [
+            "inherit",
+            "on",
+            "off"
+          ],
+          "markdownEnumDescriptions": [
+            "Color decorators are rendered if `editor.colorDecorators` is enabled.",
+            "Color decorators are rendered.",
+            "Color decorators are not rendered."
+          ],
+          "default": "inherit",
+          "markdownDescription": "Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions.",
+          "scope": "language-overridable"
+        },
         "tailwindCSS.validate": {
           "type": "boolean",
           "default": true,
@@ -158,6 +174,8 @@   },
   "devDependencies": {
     "@ctrl/tinycolor": "^3.1.0",
 {
+          "type": "boolean",
+{
   "description": "Intelligent Tailwind CSS tooling for VS Code",
     "@types/moo": "^0.5.3",
     "@types/node": "^13.9.3",
@@ -167,6 +185,7 @@     "callsite": "^1.0.0",
     "chokidar": "^3.3.1",
     "concurrently": "^5.1.0",
     "css.escape": "^1.5.1",
+    "debounce": "^1.2.0",
     "detect-indent": "^6.0.0",
     "dlv": "^1.1.3",
     "dset": "^2.0.1",
M src/extension.ts -> src/extension.ts
diff --git a/src/extension.ts b/src/extension.ts
index 279ec969590ba1e30120474c179722fa450c238d..fe982e92a2882d37e61976a3f1d2733cee6e01e2 100755
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -24,6 +24,7 @@ import isObject from './util/isObject'
 import { dedupe, equal } from './util/array'
 import { createEmitter } from './lib/emitter'
 import { onMessage } from './lsp/notifications'
+import { registerColorDecorator } from './lib/registerColorDecorator'
 
 const CLIENT_ID = 'tailwindcss-intellisense'
 const CLIENT_NAME = 'Tailwind CSS IntelliSense'
@@ -152,6 +153,7 @@ 
     client.onReady().then(() => {
       let emitter = createEmitter(client)
       registerConfigErrorHandler(emitter)
+      registerColorDecorator(client, context, emitter)
       onMessage(client, 'getConfiguration', async (scope) => {
         return Workspace.getConfiguration('tailwindCSS', scope)
       })
I src/lib/registerColorDecorator.ts
diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..66d6767ee428934b351b9ccdcd884c0f236477db
--- /dev/null
+++ b/src/lib/registerColorDecorator.ts
@@ -0,0 +1,132 @@
+import { window, workspace, ExtensionContext, TextEditor } from 'vscode'
+import { NotificationEmitter } from './emitter'
+import { LanguageClient } from 'vscode-languageclient'
+import debounce from 'debounce'
+
+const colorDecorationType = window.createTextEditorDecorationType({
+  before: {
+    width: '0.8em',
+    height: '0.8em',
+    contentText: ' ',
+    border: '0.1em solid',
+    margin: '0.1em 0.2em 0',
+  },
+  dark: {
+    before: {
+      borderColor: '#eeeeee',
+    },
+  },
+  light: {
+    before: {
+      borderColor: '#000000',
+    },
+  },
+})
+
+export function registerColorDecorator(
+  client: LanguageClient,
+  context: ExtensionContext,
+  emitter: NotificationEmitter
+) {
+  let activeEditor = window.activeTextEditor
+
+  async function updateDecorations() {
+    return updateDecorationsInEditor(activeEditor)
+  }
+
+  async function updateDecorationsInEditor(editor: TextEditor) {
+    if (!editor) return
+    if (editor.document.uri.scheme !== 'file') return
+
+    let workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri)
+    if (
+      !workspaceFolder ||
+      workspaceFolder.uri.toString() !==
+        client.clientOptions.workspaceFolder.uri.toString()
+    ) {
+      return
+    }
+
+    let preference =
+      workspace.getConfiguration('tailwindCSS', editor.document)
+        .colorDecorators || 'inherit'
+
+    let enabled: boolean =
+      preference === 'inherit'
+        ? Boolean(workspace.getConfiguration('editor').colorDecorators)
+        : preference === 'on'
+
+    if (!enabled) {
+      editor.setDecorations(colorDecorationType, [])
+      return
+    }
+
+    let { colors } = await emitter.emit('getDocumentColors', {
+      document: editor.document.uri.toString(),
+    })
+
+    editor.setDecorations(
+      colorDecorationType,
+      colors
+        .filter(({ color }) => color !== 'rgba(0, 0, 0, 0.01)')
+        .map(({ range, color }) => ({
+          range,
+          renderOptions: { before: { backgroundColor: color } },
+        }))
+    )
+  }
+
+  const triggerUpdateDecorations = debounce(updateDecorations, 200)
+
+  if (activeEditor) {
+    triggerUpdateDecorations()
+  }
+
+  window.onDidChangeActiveTextEditor(
+    (editor) => {
+      activeEditor = editor
+      if (editor) {
+        triggerUpdateDecorations()
+      }
+    },
+    null,
+    context.subscriptions
+  )
+
+  workspace.onDidChangeTextDocument(
+    (event) => {
+      if (activeEditor && event.document === activeEditor.document) {
+        triggerUpdateDecorations()
+      }
+    },
+    null,
+    context.subscriptions
+  )
+
+  workspace.onDidOpenTextDocument(
+    (document) => {
+      if (activeEditor && document === activeEditor.document) {
+        triggerUpdateDecorations()
+      }
+    },
+    null,
+    context.subscriptions
+  )
+
+  workspace.onDidChangeConfiguration((e) => {
+    if (
+      e.affectsConfiguration('editor.colorDecorators') ||
+      e.affectsConfiguration('tailwindCSS.colorDecorators')
+    ) {
+      window.visibleTextEditors.forEach(updateDecorationsInEditor)
+    }
+  })
+
+  emitter.on('configUpdated', () => {
+    window.visibleTextEditors.forEach(updateDecorationsInEditor)
+  })
+
+  emitter.on('configError', () => {
+    window.visibleTextEditors.forEach(updateDecorationsInEditor)
+  })
+}
I src/lsp/providers/documentColorProvider.ts
diff --git a/src/lsp/providers/documentColorProvider.ts b/src/lsp/providers/documentColorProvider.ts
new file mode 100644
index 0000000000000000000000000000000000000000..688ee746ab6a01dca0217c5515e14e599bb1f235
--- /dev/null
+++ b/src/lsp/providers/documentColorProvider.ts
@@ -0,0 +1,49 @@
+import { onMessage } from '../notifications'
+import { State } from '../util/state'
+import {
+  findClassListsInDocument,
+  getClassNamesInClassList,
+  findHelperFunctionsInDocument,
+} from '../util/find'
+import { getClassNameParts } from '../util/getClassNameAtPosition'
+import { getColor, getColorFromValue } from '../util/color'
+import { stringToPath } from '../util/stringToPath'
+const dlv = require('dlv')
+
+export function registerDocumentColorProvider(state: State) {
+  onMessage(
+    state.editor.connection,
+    'getDocumentColors',
+    async ({ document }) => {
+      let colors = []
+      if (!state.enabled) return { colors }
+      let doc = state.editor.documents.get(document)
+      if (!doc) return { colors }
+
+      let classLists = findClassListsInDocument(state, doc)
+      classLists.forEach((classList) => {
+        let classNames = getClassNamesInClassList(classList)
+        classNames.forEach((className) => {
+          let parts = getClassNameParts(state, className.className)
+          if (!parts) return
+          let color = getColor(state, parts)
+          if (!color) return
+          colors.push({ range: className.range, color: color.documentation })
+        })
+      })
+
+      let helperFns = findHelperFunctionsInDocument(state, doc)
+      helperFns.forEach((fn) => {
+        let keys = stringToPath(fn.value)
+        let base = fn.helper === 'theme' ? ['theme'] : []
+        let value = dlv(state.config, [...base, ...keys])
+        let color = getColorFromValue(value)
+        if (color) {
+          colors.push({ range: fn.valueRange, color })
+        }
+      })
+
+      return { colors }
+    }
+  )
+}
M src/lsp/server.ts -> src/lsp/server.ts
diff --git a/src/lsp/server.ts b/src/lsp/server.ts
index 4b149f82663e120586d51e1026c6f9f8eaf552f7..c4cb2d1dab02b93616d6f761d90e1ef7050dadd9 100644
--- a/src/lsp/server.ts
+++ b/src/lsp/server.ts
@@ -35,9 +35,10 @@   clearAllDiagnostics,
 } from './providers/diagnostics/diagnosticsProvider'
 import { createEmitter } from '../lib/emitter'
 import { provideCodeActions } from './providers/codeActions/codeActionProvider'
+import { registerDocumentColorProvider } from './providers/documentColorProvider'
 
 let connection = createConnection(ProposedFeatures.all)
-let state: State = { enabled: false, emitter: createEmitter(connection) }
+const state: State = { enabled: false, emitter: createEmitter(connection) }
 let documents = new TextDocuments()
 let workspaceFolder: string | null
 
@@ -73,7 +74,7 @@ connection.onInitialize(
   async (params: InitializeParams): Promise<InitializeResult> => {
     const capabilities = params.capabilities
 
-import {
+  CodeActionParams,
   createConnection,
       connection,
       documents,
@@ -100,13 +101,9 @@       {
         // @ts-ignore
         onChange: (newState: State): void => {
           if (newState && !newState.error) {
-            state = {
-              ...newState,
-              enabled: true,
-              emitter: state.emitter,
-              editor: editorState,
+/* --------------------------------------------------------------------------------------------
   ProposedFeatures,
-  createConnection,
+  TextDocuments,
             connection.sendNotification('tailwindcss/configUpdated', [
               state.configPath,
               state.config,
@@ -114,14 +111,9 @@               state.plugins,
             ])
             updateAllDiagnostics(state)
           } else {
-            state = {
 /* --------------------------------------------------------------------------------------------
-
   ProposedFeatures,
-
   ProposedFeatures,
-import {
-            }
             if (newState && newState.error) {
               const payload: {
                 message: string
@@ -145,21 +137,11 @@       }
     )
 
     if (tailwindState) {
-/* --------------------------------------------------------------------------------------------
  * Copyright (c) Microsoft Corporation. All rights reserved.
-  createConnection,
-        enabled: true,
-        emitter: state.emitter,
-  InitializeResult,
   InitializeResult,
-/* --------------------------------------------------------------------------------------------
-/* --------------------------------------------------------------------------------------------
  * Copyright (c) Microsoft Corporation. All rights reserved.
- * ------------------------------------------------------------------------------------------ */
-  InitializeResult,
  * Copyright (c) Microsoft Corporation. All rights reserved.
 /* --------------------------------------------------------------------------------------------
-} from './providers/diagnostics/diagnosticsProvider'
     }
 
     return {
@@ -206,6 +187,8 @@       state.configPath,
       state.config,
       state.plugins,
     ])
+
+    registerDocumentColorProvider(state)
   })
 
 connection.onDidChangeConfiguration((change) => {
M src/lsp/util/color.ts -> src/lsp/util/color.ts
diff --git a/src/lsp/util/color.ts b/src/lsp/util/color.ts
index 95d54170555430e9334be51c46aee88876164365..31d56ec33a369f671737f625a3d61c8960ca7d4b 100644
--- a/src/lsp/util/color.ts
+++ b/src/lsp/util/color.ts
@@ -48,21 +48,50 @@     propsToCheck.map((prop) => ensureArray(item[prop]).map(createColor))
   )
 
   // check that all of the values are valid colors
-  if (colors.some((color) => !color.isValid)) {
+  if (colors.some((color) => color !== 'transparent' && !color.isValid)) {
     return null
   }
 
+const COLOR_PROPS = [
 import { TinyColor } from '@ctrl/tinycolor'
+  const colorStrings = dedupe(
+const COLOR_PROPS = [
 
-import { TinyColor } from '@ctrl/tinycolor'
 const COLOR_PROPS = [
+const COLOR_PROPS = [
+        ? 'transparent'
+        : `${color.r}-${color.g}-${color.b}`
+    )
 import { TinyColor } from '@ctrl/tinycolor'
+import removeMeta from './removeMeta'
   'caret-color',
+const dlv = require('dlv')
     return null
   }
 
+  if (colorStrings[0] === 'transparent') {
+    return {
+  'caret-color',
 import { TinyColor } from '@ctrl/tinycolor'
+    }
+import removeMeta from './removeMeta'
   'color',
+
+  const nonTransparentColors = colors.filter(
+    (color): color is TinyColor => color !== 'transparent'
+  )
+
+  const alphas = dedupe(nonTransparentColors.map((color) => color.a))
+
+  if (alphas.length === 1 || (alphas.length === 2 && alphas.includes(0))) {
+    return {
+      documentation: nonTransparentColors
+        .find((color) => color.a !== 0)
+        .toRgbString(),
+    }
+  }
+
+  return null
 }
 
 export function getColorFromValue(value: unknown): string {
@@ -77,10 +106,10 @@   }
   return null
 }
 
-import { ensureArray, dedupe, flatten } from '../../util/array'
   'color',
+import removeMeta from './removeMeta'
   if (str === 'transparent') {
-    return new TinyColor({ r: 0, g: 0, b: 0, a: 0.01 })
+    return 'transparent'
   }
 
   // matches: rgba(<r>, <g>, <b>, var(--bg-opacity))
M src/lsp/util/find.ts -> src/lsp/util/find.ts
diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts
index db5609e3f06571bde42d9a555268d95f6318fc47..8ff4dedcaaa9d3ab1e20e53208b641fde0bf8a89 100644
--- a/src/lsp/util/find.ts
+++ b/src/lsp/util/find.ts
@@ -1,5 +1,11 @@
 import { TextDocument, Range, Position } from 'vscode-languageserver'
+import {
 import { DocumentClassName, DocumentClassList, State } from './state'
+  getClassAttributeLexer,
+  DocumentClassList,
+  State,
+  DocumentHelperFunction,
+} from './state'
 import lineColumn from 'line-column'
 import { isCssContext, isCssDoc } from './css'
 import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
@@ -11,6 +17,7 @@   getClassAttributeLexer,
   getComputedClassAttributeLexer,
 } from './lexers'
 import { getLanguageBoundaries } from './getLanguageBoundaries'
+import { resolveRange } from './resolveRange'
 
 export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
   let match: RegExpMatchArray
@@ -252,6 +259,64 @@   return flatten([
     ...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
     ...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
   ])
+}
+
+export function findHelperFunctionsInDocument(
+  state: State,
+  doc: TextDocument
+): DocumentHelperFunction[] {
+  if (isCssDoc(state, doc)) {
+    return findHelperFunctionsInRange(doc)
+  }
+
+  let boundaries = getLanguageBoundaries(state, doc)
+  if (!boundaries) return []
+
+  return flatten(
+    boundaries.css.map((range) => findHelperFunctionsInRange(doc, range))
+  )
+}
+
+export function findHelperFunctionsInRange(
+  doc: TextDocument,
+  range?: Range
+): DocumentHelperFunction[] {
+  const text = doc.getText(range)
+  const matches = findAll(
+    /(?<before>^|\s)(?<helper>theme|config)\((?:(?<single>')([^']+)'|(?<double>")([^"]+)")\)/gm,
+    text
+  )
+
+  return matches.map((match) => {
+    let value = match[4] || match[6]
+    let startIndex = match.index + match.groups.before.length
+    return {
+      full: match[0].substr(match.groups.before.length),
+      value,
+      helper: match.groups.helper === 'theme' ? 'theme' : 'config',
+      quotes: match.groups.single ? "'" : '"',
+      range: resolveRange(
+        {
+          start: indexToPosition(text, startIndex),
+          end: indexToPosition(text, match.index + match[0].length),
+        },
+        range
+      ),
+      valueRange: resolveRange(
+        {
+          start: indexToPosition(
+            text,
+            startIndex + match.groups.helper.length + 1
+          ),
+          end: indexToPosition(
+            text,
+            startIndex + match.groups.helper.length + 1 + 1 + value.length + 1
+          ),
+        },
+        range
+      ),
+    }
+  })
 }
 
 export function indexToPosition(str: string, index: number): Position {
I src/lsp/util/resolveRange.ts
diff --git a/src/lsp/util/resolveRange.ts b/src/lsp/util/resolveRange.ts
new file mode 100644
index 0000000000000000000000000000000000000000..96fe343afd5328b288c76f2fdc26e1d08ea1b004
--- /dev/null
+++ b/src/lsp/util/resolveRange.ts
@@ -0,0 +1,18 @@
+import { Range } from 'vscode-languageserver'
+
+export function resolveRange(range: Range, relativeTo?: Range) {
+  return {
+    start: {
+      line: (relativeTo?.start.line || 0) + range.start.line,
+      character:
+        (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) +
+        range.start.character,
+    },
+    end: {
+      line: (relativeTo?.start.line || 0) + range.end.line,
+      character:
+        (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) +
+        range.end.character,
+    },
+  }
+}
M src/lsp/util/state.ts -> src/lsp/util/state.ts
diff --git a/src/lsp/util/state.ts b/src/lsp/util/state.ts
index 47b976d397b70e7ac0e0d26a42ee76cb54f40374..8369e920fc74458c375f3cf1f6debd7b1a0f5350 100644
--- a/src/lsp/util/state.ts
+++ b/src/lsp/util/state.ts
@@ -75,6 +75,15 @@   relativeRange: Range
   classList: DocumentClassList
 }
 
+export type DocumentHelperFunction = {
+  full: string
+  helper: 'theme' | 'config'
+  value: string
+  quotes: '"' | "'"
+  range: Range
+  valueRange: Range
+}
+
 export type ClassNameMeta = {
   source: 'base' | 'components' | 'utilities'
   pseudo: string[]