Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
Add initial color decorators
Brad Cornes <bradlc41@gmail.com>
4 years ago
8 changed files, 300 additions(+), 0 deletions(-)
M package.json -> package.json
diff --git a/package.json b/package.json
index 0cdb709c6539c495c4114edaaf2222479b237252..13c2d39a2339a7689dcc9d6977af087000115b06 100755
--- a/package.json
+++ b/package.json
@@ -71,6 +71,21 @@           },
           "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.enabled": {
+          "type": "boolean",
+          "default": true,
+          "scope": "language-overridable"
+        },
+        "tailwindCSS.colorDecorators.classes": {
+          "type": "boolean",
+          "default": true,
+          "scope": "language-overridable"
+        },
+        "tailwindCSS.colorDecorators.cssHelpers": {
+          "type": "boolean",
+          "default": true,
+          "scope": "language-overridable"
+        },
         "tailwindCSS.validate": {
           "type": "boolean",
           "default": true,
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..cb01a7bb366da75bd8453c6f90d5a79b312f4daa
--- /dev/null
+++ b/src/lib/registerColorDecorator.ts
@@ -0,0 +1,125 @@
+import { window, workspace, ExtensionContext, TextEditor } from 'vscode'
+import { NotificationEmitter } from './emitter'
+import { LanguageClient } from 'vscode-languageclient'
+
+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
+  let timeout: NodeJS.Timer | undefined = undefined
+
+  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 settings = workspace.getConfiguration(
+      'tailwindCSS.colorDecorators',
+      editor.document
+    )
+
+    if (settings.enabled !== true) {
+      editor.setDecorations(colorDecorationType, [])
+      return
+    }
+
+    let { colors } = await emitter.emit('getDocumentColors', {
+      document: editor.document.uri.toString(),
+      classes: settings.classes,
+      cssHelpers: settings.cssHelpers,
+    })
+
+    editor.setDecorations(
+      colorDecorationType,
+      colors
+        .filter(({ color }) => color !== 'rgba(0, 0, 0, 0.01)')
+        .map(({ range, color }) => ({
+          range,
+          renderOptions: { before: { backgroundColor: color } },
+        }))
+    )
+  }
+
+  function triggerUpdateDecorations() {
+    if (timeout) {
+      clearTimeout(timeout)
+      timeout = undefined
+    }
+    timeout = setTimeout(updateDecorations, 500)
+  }
+
+  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('tailwindCSS.colorDecorators')) {
+      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..5c9e9e34917301c3ba93b1cd70a8f4bf3ce6e9a9
--- /dev/null
+++ b/src/lsp/providers/documentColorProvider.ts
@@ -0,0 +1,63 @@
+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 { logFull } from '../util/logFull'
+import { stringToPath } from '../util/stringToPath'
+const dlv = require('dlv')
+
+export function registerDocumentColorProvider(state: State) {
+  onMessage(
+    state.editor.connection,
+    'getDocumentColors',
+    async ({ document, classes, cssHelpers }) => {
+      let colors = []
+      let doc = state.editor.documents.get(document)
+      if (!doc) return { colors }
+
+      if (classes) {
+        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 })
+          })
+        })
+      }
+
+      if (cssHelpers) {
+        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: {
+            //     start: {
+            //       line: fn.valueRange.start.line,
+            //       character: fn.valueRange.start.character + 1,
+            //     },
+            //     end: fn.valueRange.end,
+            //   },
+            //   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..bd7cc22733c16d072d3935776c5c4d24c857855b 100644
--- a/src/lsp/server.ts
+++ b/src/lsp/server.ts
@@ -35,6 +35,7 @@   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) }
@@ -195,6 +196,8 @@       state.configPath,
       state.config,
       state.plugins,
     ])
+
+    registerDocumentColorProvider(state)
   })
 
 connection.onDidChangeConfiguration((change) => {
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 09a02006721913e72c3a576ee1c30f997476c6b5..77afdfad4fd9faecb028f0ef8b6079fc04c34d40 100644
--- a/src/lsp/util/state.ts
+++ b/src/lsp/util/state.ts
@@ -74,6 +74,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[]